From 694fb4dfd6dd1e066ebaddac41e2324ebd2d3c41 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 24 Apr 2024 12:12:10 -0400 Subject: [PATCH 01/28] chore: remove or rename event observer config fields removed - `cache_path`, `data_handler_tx`, and`ingestion_port` renamed - `display_logs` -> `display_stacks_ingestion_logs` --- components/chainhook-cli/src/config/mod.rs | 5 +--- components/chainhook-sdk/src/observer/mod.rs | 28 ++++--------------- .../chainhook-sdk/src/observer/tests/mod.rs | 5 +--- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/components/chainhook-cli/src/config/mod.rs b/components/chainhook-cli/src/config/mod.rs index 812d76021..1457dade2 100644 --- a/components/chainhook-cli/src/config/mod.rs +++ b/components/chainhook-cli/src/config/mod.rs @@ -115,16 +115,13 @@ impl Config { EventObserverConfig { bitcoin_rpc_proxy_enabled: true, chainhook_config: None, - ingestion_port: DEFAULT_INGESTION_PORT, bitcoind_rpc_username: self.network.bitcoind_rpc_username.clone(), bitcoind_rpc_password: self.network.bitcoind_rpc_password.clone(), bitcoind_rpc_url: self.network.bitcoind_rpc_url.clone(), bitcoin_block_signaling: self.network.bitcoin_block_signaling.clone(), - display_logs: false, - cache_path: self.storage.working_dir.clone(), + display_stacks_ingestion_logs: false, bitcoin_network: self.network.bitcoin_network.clone(), stacks_network: self.network.stacks_network.clone(), - data_handler_tx: None, prometheus_monitoring_port: self.monitoring.prometheus_monitoring_port, } } diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 4f5197371..80c4faa0f 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -39,7 +39,6 @@ use rocket::Shutdown; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::error::Error; use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; use std::str; use std::str::FromStr; use std::sync::mpsc::{Receiver, Sender}; @@ -70,16 +69,13 @@ pub enum DataHandlerEvent { pub struct EventObserverConfig { pub chainhook_config: Option, pub bitcoin_rpc_proxy_enabled: bool, - pub ingestion_port: u16, pub bitcoind_rpc_username: String, pub bitcoind_rpc_password: String, pub bitcoind_rpc_url: String, pub bitcoin_block_signaling: BitcoinBlockSignaling, - pub display_logs: bool, - pub cache_path: String, + pub display_stacks_ingestion_logs: bool, pub bitcoin_network: BitcoinNetwork, pub stacks_network: StacksNetwork, - pub data_handler_tx: Option>, pub prometheus_monitoring_port: Option, } @@ -91,19 +87,12 @@ pub struct EventObserverConfigOverrides { pub bitcoind_rpc_url: Option, pub bitcoind_zmq_url: Option, pub stacks_node_rpc_url: Option, - pub display_logs: Option, - pub cache_path: Option, + pub display_stacks_ingestion_logs: Option, pub bitcoin_network: Option, pub stacks_network: Option, } impl EventObserverConfig { - pub fn get_cache_path_buf(&self) -> PathBuf { - let mut path_buf = PathBuf::new(); - path_buf.push(&self.cache_path); - path_buf - } - pub fn get_bitcoin_config(&self) -> BitcoinConfig { let bitcoin_config = BitcoinConfig { username: self.bitcoind_rpc_username.clone(), @@ -162,9 +151,6 @@ impl EventObserverConfig { let config = EventObserverConfig { bitcoin_rpc_proxy_enabled: false, chainhook_config: None, - ingestion_port: overrides - .and_then(|c| c.ingestion_port) - .unwrap_or(DEFAULT_INGESTION_PORT), bitcoind_rpc_username: overrides .and_then(|c| c.bitcoind_rpc_username.clone()) .unwrap_or("devnet".to_string()), @@ -185,13 +171,11 @@ impl EventObserverConfig { .and_then(|c| c.ingestion_port) .unwrap_or(DEFAULT_INGESTION_PORT), ))), - display_logs: overrides.and_then(|c| c.display_logs).unwrap_or(false), - cache_path: overrides - .and_then(|c| c.cache_path.clone()) - .unwrap_or("cache".to_string()), + display_stacks_ingestion_logs: overrides + .and_then(|c| c.display_stacks_ingestion_logs) + .unwrap_or(false), bitcoin_network, stacks_network, - data_handler_tx: None, prometheus_monitoring_port: None, }; Ok(config) @@ -603,7 +587,7 @@ pub async fn start_stacks_event_observer( indexer.seed_stacks_block_pool(stacks_startup_context.block_pool_seed, &ctx); - let log_level = if config.display_logs { + let log_level = if config.display_stacks_ingestion_logs { if cfg!(feature = "cli") { LogLevel::Critical } else { diff --git a/components/chainhook-sdk/src/observer/tests/mod.rs b/components/chainhook-sdk/src/observer/tests/mod.rs index 557854726..2bb91c299 100644 --- a/components/chainhook-sdk/src/observer/tests/mod.rs +++ b/components/chainhook-sdk/src/observer/tests/mod.rs @@ -33,18 +33,15 @@ fn generate_test_config() -> (EventObserverConfig, ChainhookStore) { let config: EventObserverConfig = EventObserverConfig { chainhook_config: Some(ChainhookConfig::new()), bitcoin_rpc_proxy_enabled: false, - ingestion_port: 0, bitcoind_rpc_username: "user".into(), bitcoind_rpc_password: "user".into(), bitcoind_rpc_url: "http://localhost:18443".into(), - display_logs: false, + display_stacks_ingestion_logs: false, bitcoin_block_signaling: BitcoinBlockSignaling::Stacks( StacksNodeConfig::default_localhost(DEFAULT_INGESTION_PORT), ), - cache_path: "cache".into(), bitcoin_network: BitcoinNetwork::Regtest, stacks_network: StacksNetwork::Devnet, - data_handler_tx: None, prometheus_monitoring_port: None, }; let predicates = ChainhookConfig::new(); From 0947accce5a8e82cbf8caeb44cf84734fd37990b Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:35:55 -0400 Subject: [PATCH 02/28] add prometheus monitoring port to config overrides --- components/chainhook-sdk/src/observer/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 80c4faa0f..e229a3338 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -90,6 +90,7 @@ pub struct EventObserverConfigOverrides { pub display_stacks_ingestion_logs: Option, pub bitcoin_network: Option, pub stacks_network: Option, + pub prometheus_monitoring_port: Option, } impl EventObserverConfig { @@ -176,7 +177,7 @@ impl EventObserverConfig { .unwrap_or(false), bitcoin_network, stacks_network, - prometheus_monitoring_port: None, + prometheus_monitoring_port: overrides.and_then(|c| c.prometheus_monitoring_port), }; Ok(config) } From 4e997fc0225e51b1876a4182d2c73b76d2e48dfa Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:36:25 -0400 Subject: [PATCH 03/28] rename ingestion_port in config overrides --- components/chainhook-sdk/src/observer/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index e229a3338..353fed246 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -81,11 +81,11 @@ pub struct EventObserverConfig { #[derive(Deserialize, Debug, Clone)] pub struct EventObserverConfigOverrides { - pub ingestion_port: Option, pub bitcoind_rpc_username: Option, pub bitcoind_rpc_password: Option, pub bitcoind_rpc_url: Option, pub bitcoind_zmq_url: Option, + pub chainhook_stacks_block_ingestion_port: Option, pub stacks_node_rpc_url: Option, pub display_stacks_ingestion_logs: Option, pub bitcoin_network: Option, @@ -169,7 +169,7 @@ impl EventObserverConfig { .and_then(|c| c.stacks_node_rpc_url.clone()) .unwrap_or(DEFAULT_STACKS_NODE_RPC.to_string()), overrides - .and_then(|c| c.ingestion_port) + .and_then(|c| c.chainhook_stacks_block_ingestion_port) .unwrap_or(DEFAULT_INGESTION_PORT), ))), display_stacks_ingestion_logs: overrides From 3f6ef10e6adc4566e4c6f1b63a6b39647f7182b0 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:36:52 -0400 Subject: [PATCH 04/28] create `default` fn for `EventObserverConfig` --- components/chainhook-sdk/src/observer/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 353fed246..38ca6d35c 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -94,6 +94,23 @@ pub struct EventObserverConfigOverrides { } impl EventObserverConfig { + pub fn default() -> Self { + EventObserverConfig { + chainhook_config: None, + bitcoin_rpc_proxy_enabled: false, + bitcoind_rpc_username: "devnet".into(), + bitcoind_rpc_password: "devnet".into(), + bitcoind_rpc_url: "http://localhost:18443".into(), + bitcoin_block_signaling: BitcoinBlockSignaling::Stacks(StacksNodeConfig::new( + DEFAULT_STACKS_NODE_RPC.to_string(), + DEFAULT_INGESTION_PORT, + )), + display_stacks_ingestion_logs: false, + bitcoin_network: BitcoinNetwork::Regtest, + stacks_network: StacksNetwork::Devnet, + prometheus_monitoring_port: None, + } + } pub fn get_bitcoin_config(&self) -> BitcoinConfig { let bitcoin_config = BitcoinConfig { username: self.bitcoind_rpc_username.clone(), From 3b0f7a338918d5e0b55add6c78ace7cce623a2b7 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:37:15 -0400 Subject: [PATCH 05/28] create `EventObserverConfig` helper to register a predicate --- components/chainhook-sdk/src/observer/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 38ca6d35c..5b3160719 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -36,6 +36,7 @@ use rocket::config::{self, Config, LogLevel}; use rocket::data::{Limits, ToByteUnit}; use rocket::serde::Deserialize; use rocket::Shutdown; +use std::borrow::BorrowMut; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::error::Error; use std::net::{IpAddr, Ipv4Addr}; @@ -111,6 +112,18 @@ impl EventObserverConfig { prometheus_monitoring_port: None, } } + + pub fn register_predicate(&mut self, spec: ChainhookSpecification) -> Result<(), String> { + if let Some(ref mut chainhook_config) = self.chainhook_config.borrow_mut() { + chainhook_config.register_specification(spec)?; + } else { + let mut chainhook_config = ChainhookConfig::new(); + chainhook_config.register_specification(spec)?; + self.chainhook_config = Some(chainhook_config); + } + Ok(()) + } + pub fn get_bitcoin_config(&self) -> BitcoinConfig { let bitcoin_config = BitcoinConfig { username: self.bitcoind_rpc_username.clone(), From fc67dff52da16ecb0c3da287afe732aa06f22a21 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:42:39 -0400 Subject: [PATCH 06/28] feat: add `BitcoinEventObserverBuilder` struct --- components/chainhook-sdk/src/observer/mod.rs | 103 +++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 5b3160719..c6be55f7e 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -94,6 +94,109 @@ pub struct EventObserverConfigOverrides { pub prometheus_monitoring_port: Option, } +/// A builder that is used to create an [EventObserverConfig] that is tailored for use with a bitcoind node emitting events via the ZMQ interface. +/// Example usage: +/// ``` +/// let config: EventObserverConfig = +/// BitcoinEventObserverBuilder::new() +/// .rpc_password("my_password") +/// .network("mainnet") +/// .finish()?; +/// ``` +pub struct BitcoinEventObserverBuilder { + pub bitcoind_rpc_username: Option, + pub bitcoind_rpc_password: Option, + pub bitcoind_rpc_url: Option, + pub bitcoin_network: Option, + pub bitcoind_zmq_url: Option, + pub prometheus_monitoring_port: Option, +} +impl BitcoinEventObserverBuilder { + pub fn new() -> Self { + BitcoinEventObserverBuilder { + bitcoind_rpc_username: None, + bitcoind_rpc_password: None, + bitcoind_rpc_url: None, + bitcoin_network: None, + bitcoind_zmq_url: None, + prometheus_monitoring_port: None, + } + } + + /// Sets the bitcoind node's RPC username. + pub fn rpc_username(&mut self, username: &str) -> &mut Self { + self.bitcoind_rpc_username = Some(username.to_string()); + self + } + + /// Sets the bitcoind node's RPC password. + pub fn rpc_password(&mut self, password: &str) -> &mut Self { + self.bitcoind_rpc_password = Some(password.to_string()); + self + } + + /// Sets the bitcoind node's RPC url. + pub fn rpc_url(&mut self, url: &str) -> &mut Self { + self.bitcoind_rpc_url = Some(url.to_string()); + self + } + + /// Sets the bitcoind node's ZMQ url, used by the observer to receive new block events from bitcoind. + pub fn zmq_url(&mut self, url: &str) -> &mut Self { + self.bitcoind_zmq_url = Some(url.to_string()); + self + } + + /// Sets the Bitcoin network. Must be a valid bitcoin network string according to [BitcoinNetwork::from_str]. + pub fn network(&mut self, network: &str) -> &mut Self { + self.bitcoin_network = Some(network.to_string()); + self + } + + /// Sets the Prometheus monitoring port. + pub fn prometheus_monitoring_port(&mut self, port: u16) -> &mut Self { + self.prometheus_monitoring_port = Some(port); + self + } + + /// Attempts to convert a [BitcoinEventObserverBuilder] instance into an [EventObserverConfig], filling in + /// defaults as necessary according to [EventObserverConfig::default]. + /// + /// This function will return an error if the `bitcoin_network` string is set and is not a valid [BitcoinNetwork]. + pub fn finish(&self) -> Result { + let bitcoin_network = if let Some(network) = self.bitcoin_network.as_ref() { + BitcoinNetwork::from_str(&network)? + } else { + BitcoinNetwork::Regtest + }; + Ok(EventObserverConfig { + chainhook_config: None, + bitcoin_rpc_proxy_enabled: false, + bitcoind_rpc_username: self + .bitcoind_rpc_username + .clone() + .unwrap_or("devnet".into()), + bitcoind_rpc_password: self + .bitcoind_rpc_password + .clone() + .unwrap_or("devnet".into()), + bitcoind_rpc_url: self + .bitcoind_rpc_url + .clone() + .unwrap_or("http://localhost:18443".into()), + bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ( + self.bitcoind_zmq_url + .clone() + .unwrap_or("tcp://0.0.0.0:18543".into()), + ), + display_stacks_ingestion_logs: false, + bitcoin_network: bitcoin_network, + stacks_network: StacksNetwork::Devnet, + prometheus_monitoring_port: self.prometheus_monitoring_port, + }) + } +} + impl EventObserverConfig { pub fn default() -> Self { EventObserverConfig { From 6d6f9fe643a39bccc865016540212d677d2bb141 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:43:07 -0400 Subject: [PATCH 07/28] add impl fns to `EventObserverConfigOverrides` to allow using as builder --- components/chainhook-sdk/src/observer/mod.rs | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index c6be55f7e..c866ef13b 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -94,6 +94,93 @@ pub struct EventObserverConfigOverrides { pub prometheus_monitoring_port: Option, } +impl EventObserverConfigOverrides { + pub fn new() -> Self { + EventObserverConfigOverrides { + bitcoind_rpc_username: None, + bitcoind_rpc_password: None, + bitcoind_rpc_url: None, + bitcoind_zmq_url: None, + chainhook_stacks_block_ingestion_port: None, + stacks_node_rpc_url: None, + display_stacks_ingestion_logs: None, + bitcoin_network: None, + stacks_network: None, + prometheus_monitoring_port: None, + } + } + + /// Sets the bitcoind node's RPC username. + pub fn bitcoind_rpc_username(&mut self, username: &str) -> &mut Self { + self.bitcoind_rpc_username = Some(username.to_string()); + self + } + + /// Sets the bitcoind node's RPC password. + pub fn bitcoind_rpc_password(&mut self, password: &str) -> &mut Self { + self.bitcoind_rpc_password = Some(password.to_string()); + self + } + + /// Sets the bitcoind node's RPC url. + pub fn bitcoind_rpc_url(&mut self, url: &str) -> &mut Self { + self.bitcoind_rpc_url = Some(url.to_string()); + self + } + + /// Sets the bitcoind node's ZMQ url, used by the observer to receive new block events from bitcoind. + pub fn bitcoind_zmq_url(&mut self, url: &str) -> &mut Self { + self.bitcoind_zmq_url = Some(url.to_string()); + self + } + + /// Sets the Bitcoin network. Must be a valid bitcoin network string according to [BitcoinNetwork::from_str]. + pub fn bitcoin_network(&mut self, network: &str) -> &mut Self { + self.bitcoin_network = Some(network.to_string()); + self + } + + /// Sets the Stacks network. Must be a valid bitcoin network string according to [StacksNetwork::from_str]. + pub fn stacks_network(&mut self, network: &str) -> &mut Self { + self.stacks_network = Some(network.to_string()); + self + } + + /// Sets the Stacks node's RPC url. + pub fn stacks_node_rpc_url(&mut self, url: &str) -> &mut Self { + self.stacks_node_rpc_url = Some(url.to_string()); + self + } + + /// Sets the port at which Chainhook will observer Stacks blockchain events. The Stacks node's config should have an events_observer + /// entry matching this port in order to send block events the Chainhook. + pub fn chainhook_stacks_block_ingestion_port(&mut self, port: u16) -> &mut Self { + self.chainhook_stacks_block_ingestion_port = Some(port); + self + } + + /// Sets whether Chainhook should display Stacks ingestion logs. + pub fn display_stacks_ingestion_logs(&mut self, display_logs: bool) -> &mut Self { + self.display_stacks_ingestion_logs = Some(display_logs); + self + } + + /// Sets the Prometheus monitoring port. + pub fn prometheus_monitoring_port(&mut self, port: u16) -> &mut Self { + self.prometheus_monitoring_port = Some(port); + self + } + + /// Attempts to convert a [EventObserverConfigOverrides] instance into an [EventObserverConfig], filling in + /// defaults as necessary according to [EventObserverConfig::default]. + /// + /// This function will return an error if the `bitcoin_network` or `stacks_network` strings are set and are not a valid [BitcoinNetwork] or [StacksNetwork]. + /// + pub fn finish(&self) -> Result { + EventObserverConfig::new_using_overrides(Some(self)) + } +} + /// A builder that is used to create an [EventObserverConfig] that is tailored for use with a bitcoind node emitting events via the ZMQ interface. /// Example usage: /// ``` From 9da117847fb028e80c722b58678aa6291f68f649 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:49:16 -0400 Subject: [PATCH 08/28] rename EventObserverConfigOverrides to EventObserverConfigBuilder --- components/chainhook-sdk/src/observer/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index c866ef13b..fbbcefaac 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -81,7 +81,7 @@ pub struct EventObserverConfig { } #[derive(Deserialize, Debug, Clone)] -pub struct EventObserverConfigOverrides { +pub struct EventObserverConfigBuilder { pub bitcoind_rpc_username: Option, pub bitcoind_rpc_password: Option, pub bitcoind_rpc_url: Option, @@ -94,9 +94,9 @@ pub struct EventObserverConfigOverrides { pub prometheus_monitoring_port: Option, } -impl EventObserverConfigOverrides { +impl EventObserverConfigBuilder { pub fn new() -> Self { - EventObserverConfigOverrides { + EventObserverConfigBuilder { bitcoind_rpc_username: None, bitcoind_rpc_password: None, bitcoind_rpc_url: None, @@ -353,7 +353,7 @@ impl EventObserverConfig { /// /// *Note: This is used by external crates, so it should not be removed, even if not used internally by Chainhook.* pub fn new_using_overrides( - overrides: Option<&EventObserverConfigOverrides>, + overrides: Option<&EventObserverConfigBuilder>, ) -> Result { let bitcoin_network = if let Some(network) = overrides.and_then(|c| c.bitcoin_network.as_ref()) { From bc7c735066df34f3c12ce3aaebe502babe1622c5 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 12:49:29 -0400 Subject: [PATCH 09/28] rsdoc comments --- components/chainhook-sdk/src/observer/mod.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index fbbcefaac..351c12bb3 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -80,6 +80,17 @@ pub struct EventObserverConfig { pub prometheus_monitoring_port: Option, } +/// A builder that is used to create a general purpose [EventObserverConfig]. +/// +/// ## Examples +/// ``` +/// let config: EventObserverConfig = +/// EventObserverConfigBuilder::new() +/// .bitcoind_rpc_password("my_password") +/// .bitcoin_network("mainnet") +/// .stacks_network("mainnet") +/// .finish()?; +/// ``` #[derive(Deserialize, Debug, Clone)] pub struct EventObserverConfigBuilder { pub bitcoind_rpc_username: Option, @@ -182,7 +193,8 @@ impl EventObserverConfigBuilder { } /// A builder that is used to create an [EventObserverConfig] that is tailored for use with a bitcoind node emitting events via the ZMQ interface. -/// Example usage: +/// +/// ## Examples /// ``` /// let config: EventObserverConfig = /// BitcoinEventObserverBuilder::new() From 5a4cb39c7a294b3a5768d8ee9378ebe2bfbe578e Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 13:16:35 -0400 Subject: [PATCH 10/28] chore: rename structs `*ChainhookFullSpecification` => `*ChainhookSpecificationNetworkMap` `*ChainhookNetworkSpecification` => `*ChainhookSpecification` `*ChainhookSpecification` => `*ChainhookInstance` --- components/chainhook-cli/src/cli/mod.rs | 60 ++++++----- components/chainhook-cli/src/scan/bitcoin.rs | 6 +- components/chainhook-cli/src/scan/stacks.rs | 6 +- .../chainhook-cli/src/service/http_api.rs | 24 ++--- components/chainhook-cli/src/service/mod.rs | 30 +++--- .../chainhook-cli/src/service/runloops.rs | 10 +- .../src/service/tests/helpers/mock_service.rs | 10 +- .../chainhook-cli/src/service/tests/mod.rs | 6 +- .../src/service/tests/observer_tests.rs | 7 +- .../src/chainhooks/bitcoin/mod.rs | 8 +- .../src/chainhooks/bitcoin/tests.rs | 4 +- .../src/chainhooks/stacks/mod.rs | 12 +-- .../chainhook-sdk/src/chainhooks/tests/mod.rs | 16 +-- .../chainhook-sdk/src/chainhooks/types.rs | 101 +++++++++--------- components/chainhook-sdk/src/observer/mod.rs | 30 +++--- 15 files changed, 164 insertions(+), 166 deletions(-) diff --git a/components/chainhook-cli/src/cli/mod.rs b/components/chainhook-cli/src/cli/mod.rs index 005c4662a..bf3c7fb4f 100644 --- a/components/chainhook-cli/src/cli/mod.rs +++ b/components/chainhook-cli/src/cli/mod.rs @@ -16,9 +16,9 @@ use crate::storage::{ }; use chainhook_sdk::chainhooks::types::{ - BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinPredicateType, - ChainhookFullSpecification, FileHook, HookAction, InscriptionFeedData, OrdinalOperations, - StacksChainhookFullSpecification, StacksChainhookNetworkSpecification, StacksPredicate, + BitcoinChainhookSpecification, BitcoinChainhookSpecificationNetworkMap, BitcoinPredicateType, + ChainhookSpecificationNetworkMap, FileHook, HookAction, InscriptionFeedData, OrdinalOperations, + StacksChainhookSpecification, StacksChainhookSpecificationNetworkMap, StacksPredicate, StacksPrintEventBasedPredicate, }; use chainhook_sdk::types::{BitcoinNetwork, BlockIdentifier, StacksNetwork}; @@ -351,7 +351,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { .predicates_paths .iter() .map(|p| load_predicate_from_path(p)) - .collect::, _>>()?; + .collect::, _>>()?; info!(ctx.expect_logger(), "Starting service...",); @@ -384,7 +384,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { (true, false) => { let mut networks = BTreeMap::new(); - networks.insert(StacksNetwork::Testnet, StacksChainhookNetworkSpecification { + networks.insert(StacksNetwork::Testnet, StacksChainhookSpecification { start_block: Some(34239), end_block: Some(50000), blocks: None, @@ -401,7 +401,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { }) }); - networks.insert(StacksNetwork::Mainnet, StacksChainhookNetworkSpecification { + networks.insert(StacksNetwork::Mainnet, StacksChainhookSpecification { start_block: Some(34239), end_block: Some(50000), blocks: None, @@ -418,20 +418,22 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { }) }); - ChainhookFullSpecification::Stacks(StacksChainhookFullSpecification { - uuid: id.to_string(), - owner_uuid: None, - name: "Hello world".into(), - version: 1, - networks, - }) + ChainhookSpecificationNetworkMap::Stacks( + StacksChainhookSpecificationNetworkMap { + uuid: id.to_string(), + owner_uuid: None, + name: "Hello world".into(), + version: 1, + networks, + }, + ) } (false, true) => { let mut networks = BTreeMap::new(); networks.insert( BitcoinNetwork::Mainnet, - BitcoinChainhookNetworkSpecification { + BitcoinChainhookSpecification { start_block: Some(767430), end_block: Some(767430), blocks: None, @@ -451,13 +453,15 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { }, ); - ChainhookFullSpecification::Bitcoin(BitcoinChainhookFullSpecification { - uuid: id.to_string(), - owner_uuid: None, - name: "Hello world".into(), - version: 1, - networks, - }) + ChainhookSpecificationNetworkMap::Bitcoin( + BitcoinChainhookSpecificationNetworkMap { + uuid: id.to_string(), + owner_uuid: None, + name: "Hello world".into(), + version: 1, + networks, + }, + ) } _ => { return Err("command `predicates new` should either provide the flag --stacks or --bitcoin".into()); @@ -500,7 +504,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { Config::default(false, cmd.testnet, cmd.mainnet, &cmd.config_path)?; let predicate = load_predicate_from_path(&cmd.predicate_path)?; match predicate { - ChainhookFullSpecification::Bitcoin(predicate) => { + ChainhookSpecificationNetworkMap::Bitcoin(predicate) => { let predicate_spec = match predicate .into_selected_network_specification(&config.network.bitcoin_network) { @@ -521,7 +525,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { ) .await?; } - ChainhookFullSpecification::Stacks(predicate) => { + ChainhookSpecificationNetworkMap::Stacks(predicate) => { let predicate_spec = match predicate .into_selected_network_specification(&config.network.stacks_network) { @@ -567,11 +571,11 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { } PredicatesCommand::Check(cmd) => { let config = Config::default(false, cmd.testnet, cmd.mainnet, &cmd.config_path)?; - let predicate: ChainhookFullSpecification = + let predicate: ChainhookSpecificationNetworkMap = load_predicate_from_path(&cmd.predicate_path)?; match predicate { - ChainhookFullSpecification::Bitcoin(predicate) => { + ChainhookSpecificationNetworkMap::Bitcoin(predicate) => { let _ = match predicate .into_selected_network_specification(&config.network.bitcoin_network) { @@ -584,7 +588,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { } }; } - ChainhookFullSpecification::Stacks(predicate) => { + ChainhookSpecificationNetworkMap::Stacks(predicate) => { let _ = match predicate .into_selected_network_specification(&config.network.stacks_network) { @@ -864,7 +868,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { pub fn load_predicate_from_path( predicate_path: &str, -) -> Result { +) -> Result { let file = std::fs::File::open(&predicate_path) .map_err(|e| format!("unable to read file {}\n{:?}", predicate_path, e))?; let mut file_reader = BufReader::new(file); @@ -872,7 +876,7 @@ pub fn load_predicate_from_path( file_reader .read_to_end(&mut file_buffer) .map_err(|e| format!("unable to read file {}\n{:?}", predicate_path, e))?; - let predicate: ChainhookFullSpecification = serde_json::from_slice(&file_buffer) + let predicate: ChainhookSpecificationNetworkMap = serde_json::from_slice(&file_buffer) .map_err(|e| format!("unable to parse json file {}\n{:?}", predicate_path, e))?; Ok(predicate) } diff --git a/components/chainhook-cli/src/scan/bitcoin.rs b/components/chainhook-cli/src/scan/bitcoin.rs index b3d3185ce..4bb819140 100644 --- a/components/chainhook-cli/src/scan/bitcoin.rs +++ b/components/chainhook-cli/src/scan/bitcoin.rs @@ -10,7 +10,7 @@ use chainhook_sdk::chainhooks::bitcoin::{ evaluate_bitcoin_chainhooks_on_chain_event, handle_bitcoin_hook_action, BitcoinChainhookOccurrence, BitcoinTriggerChainhook, }; -use chainhook_sdk::chainhooks::types::BitcoinChainhookSpecification; +use chainhook_sdk::chainhooks::types::BitcoinChainhookInstance; use chainhook_sdk::indexer; use chainhook_sdk::indexer::bitcoin::{ build_http_client, download_and_parse_block_with_retry, retrieve_block_hash_with_retry, @@ -24,7 +24,7 @@ use chainhook_sdk::utils::{file_append, send_request, Context}; use std::collections::HashMap; pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( - predicate_spec: &BitcoinChainhookSpecification, + predicate_spec: &BitcoinChainhookInstance, unfinished_scan_data: Option, config: &Config, ctx: &Context, @@ -250,7 +250,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( pub async fn process_block_with_predicates( block: BitcoinBlockData, - predicates: &Vec<&BitcoinChainhookSpecification>, + predicates: &Vec<&BitcoinChainhookInstance>, event_observer_config: &EventObserverConfig, ctx: &Context, ) -> Result { diff --git a/components/chainhook-cli/src/scan/stacks.rs b/components/chainhook-cli/src/scan/stacks.rs index b626da29d..f4d0457c0 100644 --- a/components/chainhook-cli/src/scan/stacks.rs +++ b/components/chainhook-cli/src/scan/stacks.rs @@ -23,7 +23,7 @@ use chainhook_sdk::{ use chainhook_sdk::{ chainhooks::{ stacks::{handle_stacks_hook_action, StacksChainhookOccurrence, StacksTriggerChainhook}, - types::StacksChainhookSpecification, + types::StacksChainhookInstance, }, utils::{file_append, send_request, AbstractStacksBlock}, }; @@ -166,7 +166,7 @@ pub async fn get_canonical_fork_from_tsv( } pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( - predicate_spec: &StacksChainhookSpecification, + predicate_spec: &StacksChainhookInstance, unfinished_scan_data: Option, stacks_db_conn: &DB, config: &Config, @@ -418,7 +418,7 @@ pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( } pub async fn scan_stacks_chainstate_via_csv_using_predicate( - predicate_spec: &StacksChainhookSpecification, + predicate_spec: &StacksChainhookInstance, config: &mut Config, ctx: &Context, ) -> Result { diff --git a/components/chainhook-cli/src/service/http_api.rs b/components/chainhook-cli/src/service/http_api.rs index 460b2f9a1..b3519d421 100644 --- a/components/chainhook-cli/src/service/http_api.rs +++ b/components/chainhook-cli/src/service/http_api.rs @@ -5,7 +5,7 @@ use std::{ }; use chainhook_sdk::{ - chainhooks::types::{ChainhookFullSpecification, ChainhookSpecification}, + chainhooks::types::{ChainhookSpecificationNetworkMap, ChainhookInstance}, observer::ObserverCommand, utils::Context, }; @@ -120,7 +120,7 @@ fn handle_get_predicates( #[openapi(tag = "Managing Predicates")] #[post("/v1/chainhooks", format = "application/json", data = "")] fn handle_create_predicate( - predicate: Result, rocket::serde::json::Error>, + predicate: Result, rocket::serde::json::Error>, api_config: &State, background_job_tx: &State>>>, ctx: &State, @@ -149,7 +149,7 @@ fn handle_create_predicate( if let Ok(mut predicates_db_conn) = open_readwrite_predicates_db_conn(api_config) { match get_entry_from_predicates_db( - &ChainhookSpecification::either_stx_or_btc_key(&predicate_uuid), + &ChainhookInstance::either_stx_or_btc_key(&predicate_uuid), &mut predicates_db_conn, &ctx, ) { @@ -195,7 +195,7 @@ fn handle_get_predicate( match open_readwrite_predicates_db_conn(api_config) { Ok(mut predicates_db_conn) => { let (predicate, status) = match get_entry_from_predicates_db( - &ChainhookSpecification::either_stx_or_btc_key(&predicate_uuid), + &ChainhookInstance::either_stx_or_btc_key(&predicate_uuid), &mut predicates_db_conn, &ctx, ) { @@ -281,7 +281,7 @@ pub fn get_entry_from_predicates_db( predicate_key: &str, predicate_db_conn: &mut Connection, _ctx: &Context, -) -> Result, String> { +) -> Result, String> { let entry: HashMap = predicate_db_conn.hgetall(predicate_key).map_err(|e| { format!( "unable to load chainhook associated with key {}: {}", @@ -295,7 +295,7 @@ pub fn get_entry_from_predicates_db( Some(payload) => payload, }; - let spec = ChainhookSpecification::deserialize_specification(&encoded_spec)?; + let spec = ChainhookInstance::deserialize_specification(&encoded_spec)?; let encoded_status = match entry.get("status") { None => Err(format!( @@ -313,9 +313,9 @@ pub fn get_entry_from_predicates_db( pub fn get_entries_from_predicates_db( predicate_db_conn: &mut Connection, ctx: &Context, -) -> Result, String> { +) -> Result, String> { let chainhooks_to_load: Vec = predicate_db_conn - .scan_match(ChainhookSpecification::either_stx_or_btc_key("*")) + .scan_match(ChainhookInstance::either_stx_or_btc_key("*")) .map_err(|e| format!("unable to connect to redis: {}", e.to_string()))? .into_iter() .collect(); @@ -349,7 +349,7 @@ pub fn get_entries_from_predicates_db( pub fn load_predicates_from_redis( config: &crate::config::Config, ctx: &Context, -) -> Result, String> { +) -> Result, String> { let redis_uri: &str = config.expected_api_database_uri(); let client = redis::Client::open(redis_uri) .map_err(|e| format!("unable to connect to redis: {}", e.to_string()))?; @@ -378,11 +378,11 @@ pub fn get_routes_spec() -> (Vec, OpenApi) { } fn serialized_predicate_with_status( - predicate: &ChainhookSpecification, + predicate: &ChainhookInstance, status: &PredicateStatus, ) -> JsonValue { match (predicate, status) { - (ChainhookSpecification::Stacks(spec), status) => json!({ + (ChainhookInstance::Stacks(spec), status) => json!({ "chain": "stacks", "uuid": spec.uuid, "network": spec.network, @@ -390,7 +390,7 @@ fn serialized_predicate_with_status( "status": status, "enabled": spec.enabled, }), - (ChainhookSpecification::Bitcoin(spec), status) => json!({ + (ChainhookInstance::Bitcoin(spec), status) => json!({ "chain": "bitcoin", "uuid": spec.uuid, "network": spec.network, diff --git a/components/chainhook-cli/src/service/mod.rs b/components/chainhook-cli/src/service/mod.rs index 2e74bb5bb..28784f162 100644 --- a/components/chainhook-cli/src/service/mod.rs +++ b/components/chainhook-cli/src/service/mod.rs @@ -11,9 +11,9 @@ use crate::storage::{ open_readwrite_stacks_db_conn, }; -use chainhook_sdk::chainhooks::types::{ChainhookConfig, ChainhookFullSpecification}; +use chainhook_sdk::chainhooks::types::{ChainhookConfig, ChainhookSpecificationNetworkMap}; -use chainhook_sdk::chainhooks::types::ChainhookSpecification; +use chainhook_sdk::chainhooks::types::ChainhookInstance; use chainhook_sdk::observer::{ start_event_observer, HookExpirationData, ObserverCommand, ObserverEvent, PredicateEvaluationReport, PredicateInterruptedData, StacksObserverStartupContext, @@ -39,7 +39,7 @@ impl Service { pub async fn run( &mut self, - predicates_from_startup: Vec, + predicates_from_startup: Vec, observer_commands_tx_rx: Option<(Sender, Receiver)>, ) -> Result<(), String> { let mut chainhook_config = ChainhookConfig::new(); @@ -87,7 +87,7 @@ impl Service { continue; } } - match chainhook_config.register_specification(predicate) { + match chainhook_config.register_instance(predicate) { Ok(_) => { debug!( self.ctx.expect_logger(), @@ -113,7 +113,7 @@ impl Service { if let Ok(mut predicates_db_conn) = open_readwrite_predicates_db_conn(api_config) { let uuid = predicate.get_uuid(); match get_entry_from_predicates_db( - &ChainhookSpecification::either_stx_or_btc_key(&uuid), + &ChainhookInstance::either_stx_or_btc_key(&uuid), &mut predicates_db_conn, &self.ctx, ) { @@ -128,7 +128,7 @@ impl Service { } }; } - match chainhook_config.register_full_specification( + match chainhook_config.register_instance_from_network_map( ( &self.config.network.bitcoin_network, &self.config.network.stacks_network, @@ -303,10 +303,10 @@ impl Service { for predicate_with_last_scanned_block in leftover_scans { match predicate_with_last_scanned_block { - (ChainhookSpecification::Stacks(spec), last_scanned_block) => { + (ChainhookInstance::Stacks(spec), last_scanned_block) => { let _ = stacks_scan_op_tx.send((spec, last_scanned_block)); } - (ChainhookSpecification::Bitcoin(spec), last_scanned_block) => { + (ChainhookInstance::Bitcoin(spec), last_scanned_block) => { let _ = bitcoin_scan_op_tx.send((spec, last_scanned_block)); } } @@ -353,10 +353,10 @@ impl Service { ); } match spec { - ChainhookSpecification::Stacks(predicate_spec) => { + ChainhookInstance::Stacks(predicate_spec) => { let _ = stacks_scan_op_tx.send((predicate_spec, None)); } - ChainhookSpecification::Bitcoin(predicate_spec) => { + ChainhookInstance::Bitcoin(predicate_spec) => { let _ = bitcoin_scan_op_tx.send((predicate_spec, None)); } } @@ -389,7 +389,7 @@ impl Service { else { continue; }; - let predicate_key = ChainhookSpecification::either_stx_or_btc_key(&uuid); + let predicate_key = ChainhookInstance::either_stx_or_btc_key(&uuid); let res: Result<(), redis::RedisError> = predicates_db_conn.del(predicate_key.clone()); if let Err(e) = res { @@ -725,7 +725,7 @@ fn update_status_from_report( last_triggered_height, triggered_count, }, - &(ChainhookSpecification::either_stx_or_btc_key(predicate_uuid)), + &(ChainhookInstance::either_stx_or_btc_key(predicate_uuid)), predicates_db_conn, &ctx, ); @@ -754,7 +754,7 @@ fn update_status_from_report( last_evaluated_height, evaluated_count, }, - &(ChainhookSpecification::either_stx_or_btc_key(predicate_uuid)), + &(ChainhookInstance::either_stx_or_btc_key(predicate_uuid)), predicates_db_conn, &ctx, ); @@ -767,7 +767,7 @@ fn update_status_from_report( &chain, evaluated_count, last_evaluated_height, - &(ChainhookSpecification::either_stx_or_btc_key(predicate_uuid)), + &(ChainhookInstance::either_stx_or_btc_key(predicate_uuid)), predicates_db_conn, &ctx, ); @@ -1206,7 +1206,7 @@ pub fn update_predicate_status( fn update_predicate_spec( predicate_key: &str, - spec: &ChainhookSpecification, + spec: &ChainhookInstance, predicates_db_conn: &mut Connection, ctx: &Context, ) { diff --git a/components/chainhook-cli/src/service/runloops.rs b/components/chainhook-cli/src/service/runloops.rs index a0b85b472..182a6087d 100644 --- a/components/chainhook-cli/src/service/runloops.rs +++ b/components/chainhook-cli/src/service/runloops.rs @@ -2,7 +2,7 @@ use std::sync::mpsc::Sender; use chainhook_sdk::{ chainhooks::types::{ - BitcoinChainhookSpecification, ChainhookSpecification, StacksChainhookSpecification, + BitcoinChainhookInstance, ChainhookInstance, StacksChainhookInstance, }, observer::ObserverCommand, utils::Context, @@ -24,7 +24,7 @@ use super::ScanningData; pub fn start_stacks_scan_runloop( config: &Config, stacks_scan_op_rx: crossbeam_channel::Receiver<( - StacksChainhookSpecification, + StacksChainhookInstance, Option, )>, observer_command_tx: Sender, @@ -103,7 +103,7 @@ pub fn start_stacks_scan_runloop( } if !predicate_is_expired { let _ = observer_command_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Stacks(predicate_spec), + ChainhookInstance::Stacks(predicate_spec), )); } }); @@ -114,7 +114,7 @@ pub fn start_stacks_scan_runloop( pub fn start_bitcoin_scan_runloop( config: &Config, bitcoin_scan_op_rx: crossbeam_channel::Receiver<( - BitcoinChainhookSpecification, + BitcoinChainhookInstance, Option, )>, observer_command_tx: Sender, @@ -160,7 +160,7 @@ pub fn start_bitcoin_scan_runloop( }; if !predicate_is_expired { let _ = observer_command_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Bitcoin(predicate_spec), + ChainhookInstance::Bitcoin(predicate_spec), )); } }); diff --git a/components/chainhook-cli/src/service/tests/helpers/mock_service.rs b/components/chainhook-cli/src/service/tests/helpers/mock_service.rs index 55ae3e859..bae920966 100644 --- a/components/chainhook-cli/src/service/tests/helpers/mock_service.rs +++ b/components/chainhook-cli/src/service/tests/helpers/mock_service.rs @@ -9,7 +9,7 @@ use crate::service::{ }; use chainhook_sdk::{ chainhooks::types::{ - ChainhookFullSpecification, ChainhookSpecification, StacksChainhookFullSpecification, + ChainhookInstance, ChainhookSpecificationNetworkMap, StacksChainhookSpecificationNetworkMap, }, indexer::IndexerConfig, observer::ObserverCommand, @@ -333,7 +333,7 @@ pub fn get_chainhook_config( pub async fn start_chainhook_service( config: Config, ping_startup_port: u16, - startup_predicates: Option>, + startup_predicates: Option>, ctx: &Context, ) -> Result, String> { let mut service = Service::new(config, ctx.clone()); @@ -389,8 +389,8 @@ pub struct TestSetupResult { pub async fn setup_stacks_chainhook_test( starting_chain_tip: u64, - redis_seed: Option<(StacksChainhookFullSpecification, PredicateStatus)>, - startup_predicates: Option>, + redis_seed: Option<(StacksChainhookSpecificationNetworkMap, PredicateStatus)>, + startup_predicates: Option>, ) -> TestSetupResult { let ( redis_port, @@ -433,7 +433,7 @@ pub async fn setup_stacks_chainhook_test( panic!("test failed with error: {e}"); }); - let spec = ChainhookSpecification::Stacks(stacks_spec); + let spec = ChainhookInstance::Stacks(stacks_spec); update_predicate_spec(&spec.key(), &spec, &mut connection, &ctx); update_predicate_status(&spec.key(), status, &mut connection, &ctx); } diff --git a/components/chainhook-cli/src/service/tests/mod.rs b/components/chainhook-cli/src/service/tests/mod.rs index be20a78cc..835eabdcd 100644 --- a/components/chainhook-cli/src/service/tests/mod.rs +++ b/components/chainhook-cli/src/service/tests/mod.rs @@ -1,4 +1,4 @@ -use chainhook_sdk::chainhooks::types::ChainhookFullSpecification; +use chainhook_sdk::chainhooks::types::ChainhookSpecificationNetworkMap; use chainhook_sdk::types::Chain; use chainhook_sdk::utils::Context; use rocket::serde::json::Value as JsonValue; @@ -778,7 +778,7 @@ async fn it_allows_specifying_startup_predicate() -> Result<(), String> { ); let predicate = serde_json::from_value(predicate).expect("failed to set up stacks chanhook spec for test"); - let startup_predicate = ChainhookFullSpecification::Stacks(predicate); + let startup_predicate = ChainhookSpecificationNetworkMap::Stacks(predicate); let TestSetupResult { mut redis_process, working_dir, @@ -818,7 +818,7 @@ async fn register_predicate_responds_409_if_uuid_in_use() -> Result<(), String> ); let stacks_spec = serde_json::from_value(predicate.clone()) .expect("failed to set up stacks chanhook spec for test"); - let startup_predicate = ChainhookFullSpecification::Stacks(stacks_spec); + let startup_predicate = ChainhookSpecificationNetworkMap::Stacks(stacks_spec); let TestSetupResult { mut redis_process, diff --git a/components/chainhook-cli/src/service/tests/observer_tests.rs b/components/chainhook-cli/src/service/tests/observer_tests.rs index a1ca7cc13..3924bb5a2 100644 --- a/components/chainhook-cli/src/service/tests/observer_tests.rs +++ b/components/chainhook-cli/src/service/tests/observer_tests.rs @@ -184,13 +184,12 @@ async fn it_responds_200_for_unimplemented_endpoints( body: Option<&Value>, ) { let ingestion_port = get_free_port().unwrap(); - let (working_dir, _tsv_dir) = create_tmp_working_dir().unwrap_or_else(|e| { + let (_working_dir, _tsv_dir) = create_tmp_working_dir().unwrap_or_else(|e| { panic!("test failed with error: {e}"); }); let config = EventObserverConfig { chainhook_config: None, bitcoin_rpc_proxy_enabled: false, - ingestion_port: ingestion_port, bitcoind_rpc_username: format!(""), bitcoind_rpc_password: format!(""), bitcoind_rpc_url: format!(""), @@ -200,11 +199,9 @@ async fn it_responds_200_for_unimplemented_endpoints( ingestion_port: ingestion_port, }, ), - display_logs: false, - cache_path: working_dir, + display_stacks_ingestion_logs: false, bitcoin_network: BitcoinNetwork::Regtest, stacks_network: chainhook_sdk::types::StacksNetwork::Devnet, - data_handler_tx: None, prometheus_monitoring_port: None, }; start_and_ping_event_observer(config, ingestion_port).await; diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs index 1f5b306fa..7c59ec94d 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs @@ -1,5 +1,5 @@ use super::types::{ - BitcoinChainhookSpecification, BitcoinPredicateType, DescriptorMatchingRule, ExactMatchingRule, + BitcoinChainhookInstance, BitcoinPredicateType, DescriptorMatchingRule, ExactMatchingRule, HookAction, InputPredicate, MatchingRule, OrdinalOperations, OrdinalsMetaProtocol, OutputPredicate, StacksOperations, }; @@ -28,7 +28,7 @@ use reqwest::RequestBuilder; use hex::FromHex; pub struct BitcoinTriggerChainhook<'a> { - pub chainhook: &'a BitcoinChainhookSpecification, + pub chainhook: &'a BitcoinChainhookInstance, pub apply: Vec<(Vec<&'a BitcoinTransactionData>, &'a BitcoinBlockData)>, pub rollback: Vec<(Vec<&'a BitcoinTransactionData>, &'a BitcoinBlockData)>, } @@ -95,7 +95,7 @@ pub enum BitcoinChainhookOccurrence { pub fn evaluate_bitcoin_chainhooks_on_chain_event<'a>( chain_event: &'a BitcoinChainEvent, - active_chainhooks: &Vec<&'a BitcoinChainhookSpecification>, + active_chainhooks: &Vec<&'a BitcoinChainhookInstance>, ctx: &Context, ) -> ( Vec>, @@ -226,7 +226,7 @@ pub fn serialize_bitcoin_payload_to_json<'a>( } pub fn serialize_bitcoin_transactions_to_json<'a>( - predicate_spec: &BitcoinChainhookSpecification, + predicate_spec: &BitcoinChainhookInstance, transactions: &Vec<&BitcoinTransactionData>, proofs: &HashMap<&'a TransactionIdentifier, String>, ) -> Vec { diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs index f2024a289..3ab79166f 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs @@ -164,7 +164,7 @@ fn it_serdes_occurrence_payload( 3, ); let block = generate_test_bitcoin_block(0, 0, vec![transaction.clone()], None); - let chainhook = &BitcoinChainhookSpecification { + let chainhook = &BitcoinChainhookInstance { uuid: "uuid".into(), owner_uuid: None, name: "name".into(), @@ -230,7 +230,7 @@ fn it_serdes_brc20_payload(tick: String) { let block = generate_test_bitcoin_block(0, 0, vec![transaction.clone()], None); let mut meta_protocols = HashSet::::new(); meta_protocols.insert(OrdinalsMetaProtocol::Brc20); - let chainhook = &BitcoinChainhookSpecification { + let chainhook = &BitcoinChainhookInstance { uuid: "uuid".into(), owner_uuid: None, name: "name".into(), diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index d76b7ce5f..593fd68ea 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -1,7 +1,7 @@ use crate::utils::{AbstractStacksBlock, Context}; use super::types::{ - BlockIdentifierIndexRule, ExactMatchingRule, HookAction, StacksChainhookSpecification, + BlockIdentifierIndexRule, ExactMatchingRule, HookAction, StacksChainhookInstance, StacksContractDeploymentPredicate, StacksPredicate, StacksPrintEventBasedPredicate, }; use chainhook_types::{ @@ -21,7 +21,7 @@ use reqwest::RequestBuilder; #[derive(Clone)] pub struct StacksTriggerChainhook<'a> { - pub chainhook: &'a StacksChainhookSpecification, + pub chainhook: &'a StacksChainhookInstance, pub apply: Vec<(Vec<&'a StacksTransactionData>, &'a dyn AbstractStacksBlock)>, pub rollback: Vec<(Vec<&'a StacksTransactionData>, &'a dyn AbstractStacksBlock)>, } @@ -103,7 +103,7 @@ impl<'a> StacksTriggerChainhook<'a> { pub fn evaluate_stacks_chainhooks_on_chain_event<'a>( chain_event: &'a StacksChainEvent, - active_chainhooks: Vec<&'a StacksChainhookSpecification>, + active_chainhooks: Vec<&'a StacksChainhookInstance>, ctx: &Context, ) -> ( Vec>, @@ -301,7 +301,7 @@ pub fn evaluate_stacks_chainhooks_on_chain_event<'a>( pub fn evaluate_stacks_chainhook_on_blocks<'a>( blocks: Vec<&'a dyn AbstractStacksBlock>, - chainhook: &'a StacksChainhookSpecification, + chainhook: &'a StacksChainhookInstance, ctx: &Context, ) -> ( Vec<(Vec<&'a StacksTransactionData>, &'a dyn AbstractStacksBlock)>, @@ -338,7 +338,7 @@ pub fn evaluate_stacks_chainhook_on_blocks<'a>( pub fn evaluate_stacks_predicate_on_block<'a>( block: &'a dyn AbstractStacksBlock, - chainhook: &'a StacksChainhookSpecification, + chainhook: &'a StacksChainhookInstance, _ctx: &Context, ) -> bool { match &chainhook.predicate { @@ -366,7 +366,7 @@ pub fn evaluate_stacks_predicate_on_block<'a>( pub fn evaluate_stacks_predicate_on_transaction<'a>( transaction: &'a StacksTransactionData, - chainhook: &'a StacksChainhookSpecification, + chainhook: &'a StacksChainhookInstance, ctx: &Context, ) -> bool { match &chainhook.predicate { diff --git a/components/chainhook-sdk/src/chainhooks/tests/mod.rs b/components/chainhook-sdk/src/chainhooks/tests/mod.rs index ecedbed5d..91e08bb90 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/tests/mod.rs @@ -8,7 +8,7 @@ use super::{ StacksChainhookOccurrence, StacksTriggerChainhook, }, types::{ - ExactMatchingRule, FileHook, StacksChainhookSpecification, + ExactMatchingRule, FileHook, StacksChainhookInstance, StacksContractCallBasedPredicate, StacksContractDeploymentPredicate, StacksFtEventBasedPredicate, StacksNftEventBasedPredicate, StacksPrintEventBasedPredicate, StacksTrait, @@ -389,7 +389,7 @@ fn test_stacks_predicates( confirmed_blocks: vec![], }); // Prepare predicate - let chainhook = StacksChainhookSpecification { + let chainhook = StacksChainhookInstance { uuid: "".to_string(), owner_uuid: None, name: "".to_string(), @@ -469,7 +469,7 @@ fn test_stacks_predicate_contract_deploy(predicate: StacksPredicate, expected_ap confirmed_blocks: vec![], }); // Prepare predicate - let chainhook = StacksChainhookSpecification { + let chainhook = StacksChainhookInstance { uuid: "".to_string(), owner_uuid: None, name: "".to_string(), @@ -524,7 +524,7 @@ fn verify_optional_addition_of_contract_abi() { new_blocks, confirmed_blocks: vec![], }); - let mut contract_deploy_chainhook = StacksChainhookSpecification { + let mut contract_deploy_chainhook = StacksChainhookInstance { uuid: "contract-deploy".to_string(), owner_uuid: None, name: "".to_string(), @@ -544,7 +544,7 @@ fn verify_optional_addition_of_contract_abi() { enabled: true, expired_at: None, }; - let contract_call_chainhook = StacksChainhookSpecification { + let contract_call_chainhook = StacksChainhookInstance { uuid: "contract-call".to_string(), owner_uuid: None, name: "".to_string(), @@ -663,7 +663,7 @@ fn test_stacks_predicate_contract_call(predicate: StacksPredicate, expected_appl confirmed_blocks: vec![], }); // Prepare predicate - let chainhook = StacksChainhookSpecification { + let chainhook = StacksChainhookInstance { uuid: "".to_string(), owner_uuid: None, name: "".to_string(), @@ -698,7 +698,7 @@ fn test_stacks_predicate_contract_call(predicate: StacksPredicate, expected_appl #[test] fn test_stacks_hook_action_noop() { - let chainhook = StacksChainhookSpecification { + let chainhook = StacksChainhookInstance { uuid: "".to_string(), owner_uuid: None, name: "".to_string(), @@ -756,7 +756,7 @@ fn test_stacks_hook_action_noop() { #[test] fn test_stacks_hook_action_file_append() { - let chainhook = StacksChainhookSpecification { + let chainhook = StacksChainhookInstance { uuid: "".to_string(), owner_uuid: None, name: "".to_string(), diff --git a/components/chainhook-sdk/src/chainhooks/types.rs b/components/chainhook-sdk/src/chainhooks/types.rs index d67ccd429..24863fe40 100644 --- a/components/chainhook-sdk/src/chainhooks/types.rs +++ b/components/chainhook-sdk/src/chainhooks/types.rs @@ -11,8 +11,8 @@ use crate::utils::MAX_BLOCK_HEIGHTS_ENTRIES; #[derive(Deserialize, Debug, Clone)] pub struct ChainhookConfig { - pub stacks_chainhooks: Vec, - pub bitcoin_chainhooks: Vec, + pub stacks_chainhooks: Vec, + pub bitcoin_chainhooks: Vec, } impl ChainhookConfig { @@ -23,29 +23,29 @@ impl ChainhookConfig { } } - pub fn register_full_specification( + pub fn register_instance_from_network_map( &mut self, networks: (&BitcoinNetwork, &StacksNetwork), - hook: ChainhookFullSpecification, - ) -> Result { + hook: ChainhookSpecificationNetworkMap, + ) -> Result { let spec = match hook { - ChainhookFullSpecification::Stacks(hook) => { + ChainhookSpecificationNetworkMap::Stacks(hook) => { let spec = hook.into_selected_network_specification(networks.1)?; self.stacks_chainhooks.push(spec.clone()); - ChainhookSpecification::Stacks(spec) + ChainhookInstance::Stacks(spec) } - ChainhookFullSpecification::Bitcoin(hook) => { + ChainhookSpecificationNetworkMap::Bitcoin(hook) => { let spec = hook.into_selected_network_specification(networks.0)?; self.bitcoin_chainhooks.push(spec.clone()); - ChainhookSpecification::Bitcoin(spec) + ChainhookInstance::Bitcoin(spec) } }; Ok(spec) } - pub fn enable_specification(&mut self, predicate_spec: &mut ChainhookSpecification) { + pub fn enable_instance(&mut self, predicate_spec: &mut ChainhookInstance) { match predicate_spec { - ChainhookSpecification::Stacks(spec_to_enable) => { + ChainhookInstance::Stacks(spec_to_enable) => { for spec in self.stacks_chainhooks.iter_mut() { if spec.uuid.eq(&spec_to_enable.uuid) { spec.enabled = true; @@ -54,7 +54,7 @@ impl ChainhookConfig { } } } - ChainhookSpecification::Bitcoin(spec_to_enable) => { + ChainhookInstance::Bitcoin(spec_to_enable) => { for spec in self.bitcoin_chainhooks.iter_mut() { if spec.uuid.eq(&spec_to_enable.uuid) { spec.enabled = true; @@ -66,13 +66,13 @@ impl ChainhookConfig { }; } - pub fn register_specification(&mut self, spec: ChainhookSpecification) -> Result<(), String> { + pub fn register_instance(&mut self, spec: ChainhookInstance) -> Result<(), String> { match spec { - ChainhookSpecification::Stacks(spec) => { + ChainhookInstance::Stacks(spec) => { let spec = spec.clone(); self.stacks_chainhooks.push(spec); } - ChainhookSpecification::Bitcoin(spec) => { + ChainhookInstance::Bitcoin(spec) => { let spec = spec.clone(); self.bitcoin_chainhooks.push(spec); } @@ -80,10 +80,7 @@ impl ChainhookConfig { Ok(()) } - pub fn deregister_stacks_hook( - &mut self, - hook_uuid: String, - ) -> Option { + pub fn deregister_stacks_hook(&mut self, hook_uuid: String) -> Option { let mut i = 0; while i < self.stacks_chainhooks.len() { if self.stacks_chainhooks[i].uuid == hook_uuid { @@ -99,7 +96,7 @@ impl ChainhookConfig { pub fn deregister_bitcoin_hook( &mut self, hook_uuid: String, - ) -> Option { + ) -> Option { let mut i = 0; while i < self.bitcoin_chainhooks.len() { if self.bitcoin_chainhooks[i].uuid == hook_uuid { @@ -115,7 +112,7 @@ impl ChainhookConfig { pub fn expire_stacks_hook(&mut self, hook_uuid: String, block_height: u64) { let mut i = 0; while i < self.stacks_chainhooks.len() { - if ChainhookSpecification::stacks_key(&self.stacks_chainhooks[i].uuid) == hook_uuid { + if ChainhookInstance::stacks_key(&self.stacks_chainhooks[i].uuid) == hook_uuid { self.stacks_chainhooks[i].expired_at = Some(block_height); break; } else { @@ -127,7 +124,7 @@ impl ChainhookConfig { pub fn expire_bitcoin_hook(&mut self, hook_uuid: String, block_height: u64) { let mut i = 0; while i < self.bitcoin_chainhooks.len() { - if ChainhookSpecification::bitcoin_key(&self.bitcoin_chainhooks[i].uuid) == hook_uuid { + if ChainhookInstance::bitcoin_key(&self.bitcoin_chainhooks[i].uuid) == hook_uuid { self.bitcoin_chainhooks[i].expired_at = Some(block_height); break; } else { @@ -157,12 +154,12 @@ impl Serialize for ChainhookConfig { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] -pub enum ChainhookSpecification { - Bitcoin(BitcoinChainhookSpecification), - Stacks(StacksChainhookSpecification), +pub enum ChainhookInstance { + Bitcoin(BitcoinChainhookInstance), + Stacks(StacksChainhookInstance), } -impl ChainhookSpecification { +impl ChainhookInstance { pub fn either_stx_or_btc_key(uuid: &str) -> String { format!("predicate:{}", uuid) } @@ -182,8 +179,8 @@ impl ChainhookSpecification { } } - pub fn deserialize_specification(spec: &str) -> Result { - let spec: ChainhookSpecification = serde_json::from_str(spec) + pub fn deserialize_specification(spec: &str) -> Result { + let spec: ChainhookInstance = serde_json::from_str(spec) .map_err(|e| format!("unable to deserialize predicate {}", e.to_string()))?; Ok(spec) } @@ -197,7 +194,7 @@ impl ChainhookSpecification { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct BitcoinChainhookSpecification { +pub struct BitcoinChainhookInstance { pub uuid: String, #[serde(skip_serializing_if = "Option::is_none")] pub owner_uuid: Option, @@ -222,20 +219,20 @@ pub struct BitcoinChainhookSpecification { pub expired_at: Option, } -impl BitcoinChainhookSpecification { +impl BitcoinChainhookInstance { pub fn key(&self) -> String { - ChainhookSpecification::bitcoin_key(&self.uuid) + ChainhookInstance::bitcoin_key(&self.uuid) } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case", tag = "chain")] -pub enum ChainhookFullSpecification { - Bitcoin(BitcoinChainhookFullSpecification), - Stacks(StacksChainhookFullSpecification), +pub enum ChainhookSpecificationNetworkMap { + Bitcoin(BitcoinChainhookSpecificationNetworkMap), + Stacks(StacksChainhookSpecificationNetworkMap), } -impl ChainhookFullSpecification { +impl ChainhookSpecificationNetworkMap { pub fn validate(&self) -> Result<(), String> { match &self { Self::Bitcoin(data) => { @@ -286,33 +283,33 @@ impl ChainhookFullSpecification { pub fn deserialize_specification( spec: &str, _key: &str, - ) -> Result { - let spec: ChainhookFullSpecification = serde_json::from_str(spec) + ) -> Result { + let spec: ChainhookSpecificationNetworkMap = serde_json::from_str(spec) .map_err(|e| format!("unable to deserialize predicate {}", e.to_string()))?; Ok(spec) } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct BitcoinChainhookFullSpecification { +pub struct BitcoinChainhookSpecificationNetworkMap { pub uuid: String, #[serde(skip_serializing_if = "Option::is_none")] pub owner_uuid: Option, pub name: String, pub version: u32, - pub networks: BTreeMap, + pub networks: BTreeMap, } -impl BitcoinChainhookFullSpecification { +impl BitcoinChainhookSpecificationNetworkMap { pub fn into_selected_network_specification( mut self, network: &BitcoinNetwork, - ) -> Result { + ) -> Result { let spec = self .networks .remove(network) .ok_or("Network unknown".to_string())?; - Ok(BitcoinChainhookSpecification { + Ok(BitcoinChainhookInstance { uuid: self.uuid, owner_uuid: self.owner_uuid, name: self.name, @@ -335,7 +332,7 @@ impl BitcoinChainhookFullSpecification { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct BitcoinChainhookNetworkSpecification { +pub struct BitcoinChainhookSpecification { #[serde(skip_serializing_if = "Option::is_none")] pub blocks: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -359,25 +356,25 @@ pub struct BitcoinChainhookNetworkSpecification { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct StacksChainhookFullSpecification { +pub struct StacksChainhookSpecificationNetworkMap { pub uuid: String, #[serde(skip_serializing_if = "Option::is_none")] pub owner_uuid: Option, pub name: String, pub version: u32, - pub networks: BTreeMap, + pub networks: BTreeMap, } -impl StacksChainhookFullSpecification { +impl StacksChainhookSpecificationNetworkMap { pub fn into_selected_network_specification( mut self, network: &StacksNetwork, - ) -> Result { + ) -> Result { let spec = self .networks .remove(network) .ok_or("Network unknown".to_string())?; - Ok(StacksChainhookSpecification { + Ok(StacksChainhookInstance { uuid: self.uuid, owner_uuid: self.owner_uuid, name: self.name, @@ -399,7 +396,7 @@ impl StacksChainhookFullSpecification { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct StacksChainhookNetworkSpecification { +pub struct StacksChainhookSpecification { #[serde(skip_serializing_if = "Option::is_none")] pub blocks: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -732,7 +729,7 @@ pub enum BlockIdentifierHashRule { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct StacksChainhookSpecification { +pub struct StacksChainhookInstance { pub uuid: String, #[serde(skip_serializing_if = "Option::is_none")] pub owner_uuid: Option, @@ -759,9 +756,9 @@ pub struct StacksChainhookSpecification { pub expired_at: Option, } -impl StacksChainhookSpecification { +impl StacksChainhookInstance { pub fn key(&self) -> String { - ChainhookSpecification::stacks_key(&self.uuid) + ChainhookInstance::stacks_key(&self.uuid) } pub fn is_predicate_targeting_block_header(&self) -> bool { diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 351c12bb3..7418848db 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -11,7 +11,7 @@ use crate::chainhooks::stacks::{ StacksChainhookOccurrence, StacksChainhookOccurrencePayload, }; use crate::chainhooks::types::{ - ChainhookConfig, ChainhookFullSpecification, ChainhookSpecification, + ChainhookConfig, ChainhookInstance, ChainhookSpecificationNetworkMap, }; use crate::indexer::bitcoin::{ @@ -315,12 +315,12 @@ impl EventObserverConfig { } } - pub fn register_predicate(&mut self, spec: ChainhookSpecification) -> Result<(), String> { + pub fn register_chainhook_instance(&mut self, spec: ChainhookInstance) -> Result<(), String> { if let Some(ref mut chainhook_config) = self.chainhook_config.borrow_mut() { - chainhook_config.register_specification(spec)?; + chainhook_config.register_instance(spec)?; } else { let mut chainhook_config = ChainhookConfig::new(); - chainhook_config.register_specification(spec)?; + chainhook_config.register_instance(spec)?; self.chainhook_config = Some(chainhook_config); } Ok(()) @@ -428,8 +428,8 @@ pub enum ObserverCommand { PropagateBitcoinChainEvent(BlockchainEvent), PropagateStacksChainEvent(StacksChainEvent), PropagateStacksMempoolEvent(StacksChainMempoolEvent), - RegisterPredicate(ChainhookFullSpecification), - EnablePredicate(ChainhookSpecification), + RegisterPredicate(ChainhookSpecificationNetworkMap), + EnablePredicate(ChainhookInstance), DeregisterBitcoinPredicate(String), DeregisterStacksPredicate(String), ExpireBitcoinPredicate(HookExpirationData), @@ -528,9 +528,9 @@ pub enum ObserverEvent { BitcoinChainEvent((BitcoinChainEvent, PredicateEvaluationReport)), StacksChainEvent((StacksChainEvent, PredicateEvaluationReport)), NotifyBitcoinTransactionProxied, - PredicateRegistered(ChainhookSpecification), + PredicateRegistered(ChainhookInstance), PredicateDeregistered(String), - PredicateEnabled(ChainhookSpecification), + PredicateEnabled(ChainhookInstance), BitcoinPredicateTriggered(BitcoinChainhookOccurrencePayload), StacksPredicateTriggered(StacksChainhookOccurrencePayload), PredicatesTriggered(usize), @@ -1437,7 +1437,7 @@ pub async fn start_observer_commands_handler( .deregister_bitcoin_hook(data.chainhook.uuid.clone()); if let Some(ref tx) = observer_events_tx { let _ = tx.send(ObserverEvent::PredicateInterrupted(PredicateInterruptedData { - predicate_key: ChainhookSpecification::bitcoin_key(&data.chainhook.uuid), + predicate_key: ChainhookInstance::bitcoin_key(&data.chainhook.uuid), error: format!("Unable to evaluate predicate on Bitcoin chainstate: {}", e) })); } @@ -1626,7 +1626,7 @@ pub async fn start_observer_commands_handler( .deregister_stacks_hook(data.chainhook.uuid.clone()); if let Some(ref tx) = observer_events_tx { let _ = tx.send(ObserverEvent::PredicateInterrupted(PredicateInterruptedData { - predicate_key: ChainhookSpecification::stacks_key(&data.chainhook.uuid), + predicate_key: ChainhookInstance::stacks_key(&data.chainhook.uuid), error: format!("Unable to evaluate predicate on Bitcoin chainstate: {}", e) })); } @@ -1661,7 +1661,7 @@ pub async fn start_observer_commands_handler( let mut spec = match chainhook_store .predicates - .register_full_specification(networks, spec) + .register_instance_from_network_map(networks, spec) { Ok(spec) => spec, Err(e) => { @@ -1677,10 +1677,10 @@ pub async fn start_observer_commands_handler( }; match spec { - ChainhookSpecification::Bitcoin(_) => { + ChainhookInstance::Bitcoin(_) => { prometheus_monitoring.btc_metrics_register_predicate() } - ChainhookSpecification::Stacks(_) => { + ChainhookInstance::Stacks(_) => { prometheus_monitoring.stx_metrics_register_predicate() } }; @@ -1694,12 +1694,12 @@ pub async fn start_observer_commands_handler( ctx.try_log(|logger| { slog::debug!(logger, "Enabling Predicate {}", spec.uuid()) }); - chainhook_store.predicates.enable_specification(&mut spec); + chainhook_store.predicates.enable_instance(&mut spec); } } ObserverCommand::EnablePredicate(mut spec) => { ctx.try_log(|logger| slog::info!(logger, "Enabling Predicate {}", spec.uuid())); - chainhook_store.predicates.enable_specification(&mut spec); + chainhook_store.predicates.enable_instance(&mut spec); if let Some(ref tx) = observer_events_tx { let _ = tx.send(ObserverEvent::PredicateEnabled(spec)); } From f992880c09213d4f061b36d655e6c079b5b7f3bf Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 20 Jun 2024 13:16:40 -0400 Subject: [PATCH 11/28] fix tests --- .../chainhook-sdk/src/observer/tests/mod.rs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/components/chainhook-sdk/src/observer/tests/mod.rs b/components/chainhook-sdk/src/observer/tests/mod.rs index 2bb91c299..95f4b5351 100644 --- a/components/chainhook-sdk/src/observer/tests/mod.rs +++ b/components/chainhook-sdk/src/observer/tests/mod.rs @@ -1,9 +1,9 @@ use crate::chainhooks::types::{ - BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, - BitcoinChainhookSpecification, BitcoinPredicateType, ChainhookConfig, - ChainhookFullSpecification, ChainhookSpecification, ExactMatchingRule, HookAction, - InscriptionFeedData, OrdinalOperations, OutputPredicate, StacksChainhookFullSpecification, - StacksChainhookNetworkSpecification, StacksChainhookSpecification, + BitcoinChainhookInstance, BitcoinChainhookSpecification, + BitcoinChainhookSpecificationNetworkMap, BitcoinPredicateType, ChainhookConfig, + ChainhookInstance, ChainhookSpecificationNetworkMap, ExactMatchingRule, HookAction, + InscriptionFeedData, OrdinalOperations, OutputPredicate, StacksChainhookInstance, + StacksChainhookSpecification, StacksChainhookSpecificationNetworkMap, StacksContractCallBasedPredicate, StacksPredicate, }; use crate::indexer::fork_scratch_pad::ForkScratchPad; @@ -54,11 +54,11 @@ fn stacks_chainhook_contract_call( contract_identifier: &str, expire_after_occurrence: Option, method: &str, -) -> StacksChainhookFullSpecification { +) -> StacksChainhookSpecificationNetworkMap { let mut networks = BTreeMap::new(); networks.insert( StacksNetwork::Devnet, - StacksChainhookNetworkSpecification { + StacksChainhookSpecification { start_block: None, end_block: None, blocks: None, @@ -74,7 +74,7 @@ fn stacks_chainhook_contract_call( }, ); - let spec = StacksChainhookFullSpecification { + let spec = StacksChainhookSpecificationNetworkMap { uuid: format!("{}", id), name: format!("Chainhook {}", id), owner_uuid: None, @@ -88,11 +88,11 @@ fn bitcoin_chainhook_p2pkh( id: u8, address: &str, expire_after_occurrence: Option, -) -> BitcoinChainhookFullSpecification { +) -> BitcoinChainhookSpecificationNetworkMap { let mut networks = BTreeMap::new(); networks.insert( BitcoinNetwork::Regtest, - BitcoinChainhookNetworkSpecification { + BitcoinChainhookSpecification { start_block: None, end_block: None, blocks: None, @@ -108,7 +108,7 @@ fn bitcoin_chainhook_p2pkh( }, ); - let spec = BitcoinChainhookFullSpecification { + let spec = BitcoinChainhookSpecificationNetworkMap { uuid: format!("{}", id), name: format!("Chainhook {}", id), owner_uuid: None, @@ -118,11 +118,11 @@ fn bitcoin_chainhook_p2pkh( spec } -fn bitcoin_chainhook_ordinals(id: u8) -> BitcoinChainhookFullSpecification { +fn bitcoin_chainhook_ordinals(id: u8) -> BitcoinChainhookSpecificationNetworkMap { let mut networks = BTreeMap::new(); networks.insert( BitcoinNetwork::Regtest, - BitcoinChainhookNetworkSpecification { + BitcoinChainhookSpecification { start_block: None, end_block: None, blocks: None, @@ -140,7 +140,7 @@ fn bitcoin_chainhook_ordinals(id: u8) -> BitcoinChainhookFullSpecification { }, ); - let spec = BitcoinChainhookFullSpecification { + let spec = BitcoinChainhookSpecificationNetworkMap { uuid: format!("{}", id), name: format!("Chainhook {}", id), owner_uuid: None, @@ -156,19 +156,19 @@ fn generate_and_register_new_stacks_chainhook( id: u8, contract_name: &str, method: &str, -) -> StacksChainhookSpecification { +) -> StacksChainhookInstance { let contract_identifier = format!("{}.{}", accounts::deployer_stx_address(), contract_name); let chainhook = stacks_chainhook_contract_call(id, &contract_identifier, None, method); let _ = observer_commands_tx.send(ObserverCommand::RegisterPredicate( - ChainhookFullSpecification::Stacks(chainhook.clone()), + ChainhookSpecificationNetworkMap::Stacks(chainhook.clone()), )); let mut chainhook = chainhook .into_selected_network_specification(&StacksNetwork::Devnet) .unwrap(); chainhook.enabled = true; - let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Stacks(chainhook.clone()), - )); + let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( + chainhook.clone(), + ))); assert!(match observer_events_rx.recv() { Ok(ObserverEvent::PredicateRegistered(_)) => { // assert_eq!( @@ -179,9 +179,9 @@ fn generate_and_register_new_stacks_chainhook( } _ => false, }); - let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Stacks(chainhook.clone()), - )); + let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( + chainhook.clone(), + ))); assert!(match observer_events_rx.recv() { Ok(ObserverEvent::PredicateEnabled(_)) => { // assert_eq!( @@ -201,17 +201,17 @@ fn generate_and_register_new_bitcoin_chainhook( id: u8, p2pkh_address: &str, expire_after_occurrence: Option, -) -> BitcoinChainhookSpecification { +) -> BitcoinChainhookInstance { let chainhook = bitcoin_chainhook_p2pkh(id, &p2pkh_address, expire_after_occurrence); let _ = observer_commands_tx.send(ObserverCommand::RegisterPredicate( - ChainhookFullSpecification::Bitcoin(chainhook.clone()), + ChainhookSpecificationNetworkMap::Bitcoin(chainhook.clone()), )); let mut chainhook = chainhook .into_selected_network_specification(&BitcoinNetwork::Regtest) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Bitcoin(chainhook.clone()), + ChainhookInstance::Bitcoin(chainhook.clone()), )); assert!(match observer_events_rx.recv() { Ok(ObserverEvent::PredicateRegistered(_)) => { @@ -320,17 +320,17 @@ fn generate_and_register_new_ordinals_chainhook( observer_commands_tx: &Sender, observer_events_rx: &crossbeam_channel::Receiver, id: u8, -) -> BitcoinChainhookSpecification { +) -> BitcoinChainhookInstance { let chainhook = bitcoin_chainhook_ordinals(id); let _ = observer_commands_tx.send(ObserverCommand::RegisterPredicate( - ChainhookFullSpecification::Bitcoin(chainhook.clone()), + ChainhookSpecificationNetworkMap::Bitcoin(chainhook.clone()), )); let mut chainhook = chainhook .into_selected_network_specification(&BitcoinNetwork::Regtest) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Bitcoin(chainhook.clone()), + ChainhookInstance::Bitcoin(chainhook.clone()), )); assert!(match observer_events_rx.recv() { Ok(ObserverEvent::PredicateRegistered(_)) => { @@ -579,15 +579,15 @@ fn test_stacks_chainhook_auto_deregister() { let contract_identifier = format!("{}.{}", accounts::deployer_stx_address(), "counter"); let chainhook = stacks_chainhook_contract_call(0, &contract_identifier, Some(1), "increment"); let _ = observer_commands_tx.send(ObserverCommand::RegisterPredicate( - ChainhookFullSpecification::Stacks(chainhook.clone()), + ChainhookSpecificationNetworkMap::Stacks(chainhook.clone()), )); let mut chainhook = chainhook .into_selected_network_specification(&StacksNetwork::Devnet) .unwrap(); chainhook.enabled = true; - let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( - ChainhookSpecification::Stacks(chainhook.clone()), - )); + let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( + chainhook.clone(), + ))); assert!(match observer_events_rx.recv() { Ok(ObserverEvent::PredicateRegistered(_)) => { // assert_eq!( From 39986d5d659264aab56d55e092e3eef3769f0264 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 21 Jun 2024 13:50:22 -0400 Subject: [PATCH 12/28] fix openapi.json --- docs/chainhook-openapi.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/chainhook-openapi.json b/docs/chainhook-openapi.json index c8235911f..93e9279bf 100644 --- a/docs/chainhook-openapi.json +++ b/docs/chainhook-openapi.json @@ -49,7 +49,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ChainhookFullSpecification" + "$ref": "#/components/schemas/ChainhookSpecificationNetworkMap" } } }, @@ -154,7 +154,7 @@ }, "components": { "schemas": { - "ChainhookFullSpecification": { + "ChainhookSpecificationNetworkMap": { "oneOf": [ { "type": "object", @@ -196,7 +196,7 @@ ], "properties": { "regtest": { - "$ref": "#/components/schemas/BitcoinChainhookNetworkSpecification" + "$ref": "#/components/schemas/BitcoinChainhookSpecification" } } }, @@ -207,7 +207,7 @@ ], "properties": { "testnet": { - "$ref": "#/components/schemas/BitcoinChainhookNetworkSpecification" + "$ref": "#/components/schemas/BitcoinChainhookSpecification" } } }, @@ -218,7 +218,7 @@ ], "properties": { "signet": { - "$ref": "#/components/schemas/BitcoinChainhookNetworkSpecification" + "$ref": "#/components/schemas/BitcoinChainhookSpecification" } } }, @@ -229,7 +229,7 @@ ], "properties": { "mainnet": { - "$ref": "#/components/schemas/BitcoinChainhookNetworkSpecification" + "$ref": "#/components/schemas/BitcoinChainhookSpecification" } } } @@ -277,7 +277,7 @@ ], "properties": { "simnet": { - "$ref": "#/components/schemas/StacksChainhookNetworkSpecification" + "$ref": "#/components/schemas/StacksChainhookSpecification" } } }, @@ -288,7 +288,7 @@ ], "properties": { "devnet": { - "$ref": "#/components/schemas/StacksChainhookNetworkSpecification" + "$ref": "#/components/schemas/StacksChainhookSpecification" } } }, @@ -299,7 +299,7 @@ ], "properties": { "testnet": { - "$ref": "#/components/schemas/StacksChainhookNetworkSpecification" + "$ref": "#/components/schemas/StacksChainhookSpecification" } } }, @@ -310,7 +310,7 @@ ], "properties": { "mainnet": { - "$ref": "#/components/schemas/StacksChainhookNetworkSpecification" + "$ref": "#/components/schemas/StacksChainhookSpecification" } } } @@ -329,7 +329,7 @@ "mainnet" ] }, - "BitcoinChainhookNetworkSpecification": { + "BitcoinChainhookSpecification": { "type": "object", "required": [ "if_this", @@ -857,7 +857,7 @@ "mainnet" ] }, - "StacksChainhookNetworkSpecification": { + "StacksChainhookSpecification": { "type": "object", "required": [ "if_this", From 473a8daca7edfdc58324c44d33896cf427c0b0f7 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 21 Jun 2024 15:18:25 -0400 Subject: [PATCH 13/28] fix doc comments --- components/chainhook-sdk/src/observer/mod.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 7418848db..7d7d89cd9 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -84,12 +84,16 @@ pub struct EventObserverConfig { /// /// ## Examples /// ``` -/// let config: EventObserverConfig = +/// use chainhook_sdk::observer::EventObserverConfig; +/// use chainhook_sdk::observer::EventObserverConfigBuilder; +/// +/// fn get_config() -> Result { /// EventObserverConfigBuilder::new() /// .bitcoind_rpc_password("my_password") /// .bitcoin_network("mainnet") /// .stacks_network("mainnet") -/// .finish()?; +/// .finish() +/// } /// ``` #[derive(Deserialize, Debug, Clone)] pub struct EventObserverConfigBuilder { @@ -196,11 +200,15 @@ impl EventObserverConfigBuilder { /// /// ## Examples /// ``` -/// let config: EventObserverConfig = +/// use chainhook_sdk::observer::EventObserverConfig; +/// use chainhook_sdk::observer::BitcoinEventObserverBuilder; +/// +/// fn get_config() -> Result { /// BitcoinEventObserverBuilder::new() /// .rpc_password("my_password") /// .network("mainnet") -/// .finish()?; +/// .finish() +/// } /// ``` pub struct BitcoinEventObserverBuilder { pub bitcoind_rpc_username: Option, From 83e833626950a8097aa14e54115ba6db6591d268 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 21 Jun 2024 15:18:53 -0400 Subject: [PATCH 14/28] move stacks/bitcoin specific chainhook types --- components/chainhook-cli/src/cli/mod.rs | 25 +- components/chainhook-cli/src/scan/bitcoin.rs | 2 +- components/chainhook-cli/src/scan/stacks.rs | 6 +- .../chainhook-cli/src/service/runloops.rs | 10 +- .../src/service/tests/helpers/mock_service.rs | 7 +- .../src/chainhooks/bitcoin/mod.rs | 303 ++++++++++- .../src/chainhooks/bitcoin/tests.rs | 2 +- .../src/chainhooks/stacks/mod.rs | 188 ++++++- .../chainhook-sdk/src/chainhooks/tests/mod.rs | 14 +- .../chainhook-sdk/src/chainhooks/types.rs | 484 +----------------- .../chainhook-sdk/src/indexer/bitcoin/mod.rs | 3 +- .../chainhook-sdk/src/observer/tests/mod.rs | 28 +- 12 files changed, 538 insertions(+), 534 deletions(-) diff --git a/components/chainhook-cli/src/cli/mod.rs b/components/chainhook-cli/src/cli/mod.rs index bf3c7fb4f..f41574a6e 100644 --- a/components/chainhook-cli/src/cli/mod.rs +++ b/components/chainhook-cli/src/cli/mod.rs @@ -14,13 +14,16 @@ use crate::storage::{ is_stacks_block_present, open_readonly_stacks_db_conn, open_readwrite_stacks_db_conn, set_last_confirmed_insert_key, }; - -use chainhook_sdk::chainhooks::types::{ - BitcoinChainhookSpecification, BitcoinChainhookSpecificationNetworkMap, BitcoinPredicateType, - ChainhookSpecificationNetworkMap, FileHook, HookAction, InscriptionFeedData, OrdinalOperations, - StacksChainhookSpecification, StacksChainhookSpecificationNetworkMap, StacksPredicate, - StacksPrintEventBasedPredicate, -}; +use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookSpecification; +use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookSpecificationNetworkMap; +use chainhook_sdk::chainhooks::bitcoin::BitcoinPredicateType; +use chainhook_sdk::chainhooks::bitcoin::InscriptionFeedData; +use chainhook_sdk::chainhooks::bitcoin::OrdinalOperations; +use chainhook_sdk::chainhooks::stacks::StacksChainhookSpecification; +use chainhook_sdk::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; +use chainhook_sdk::chainhooks::stacks::StacksPredicate; +use chainhook_sdk::chainhooks::stacks::StacksPrintEventBasedPredicate; +use chainhook_sdk::chainhooks::types::{ChainhookSpecificationNetworkMap, FileHook, HookAction}; use chainhook_sdk::types::{BitcoinNetwork, BlockIdentifier, StacksNetwork}; use chainhook_sdk::utils::{BlockHeights, Context}; use clap::{Parser, Subcommand}; @@ -506,7 +509,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { match predicate { ChainhookSpecificationNetworkMap::Bitcoin(predicate) => { let predicate_spec = match predicate - .into_selected_network_specification(&config.network.bitcoin_network) + .into_specification_for_network(&config.network.bitcoin_network) { Ok(predicate) => predicate, Err(e) => { @@ -527,7 +530,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { } ChainhookSpecificationNetworkMap::Stacks(predicate) => { let predicate_spec = match predicate - .into_selected_network_specification(&config.network.stacks_network) + .into_specification_from_network(&config.network.stacks_network) { Ok(predicate) => predicate, Err(e) => { @@ -577,7 +580,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { match predicate { ChainhookSpecificationNetworkMap::Bitcoin(predicate) => { let _ = match predicate - .into_selected_network_specification(&config.network.bitcoin_network) + .into_specification_for_network(&config.network.bitcoin_network) { Ok(predicate) => predicate, Err(e) => { @@ -590,7 +593,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { } ChainhookSpecificationNetworkMap::Stacks(predicate) => { let _ = match predicate - .into_selected_network_specification(&config.network.stacks_network) + .into_specification_from_network(&config.network.stacks_network) { Ok(predicate) => predicate, Err(e) => { diff --git a/components/chainhook-cli/src/scan/bitcoin.rs b/components/chainhook-cli/src/scan/bitcoin.rs index 4bb819140..31463bcf5 100644 --- a/components/chainhook-cli/src/scan/bitcoin.rs +++ b/components/chainhook-cli/src/scan/bitcoin.rs @@ -10,7 +10,7 @@ use chainhook_sdk::chainhooks::bitcoin::{ evaluate_bitcoin_chainhooks_on_chain_event, handle_bitcoin_hook_action, BitcoinChainhookOccurrence, BitcoinTriggerChainhook, }; -use chainhook_sdk::chainhooks::types::BitcoinChainhookInstance; +use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookInstance; use chainhook_sdk::indexer; use chainhook_sdk::indexer::bitcoin::{ build_http_client, download_and_parse_block_with_retry, retrieve_block_hash_with_retry, diff --git a/components/chainhook-cli/src/scan/stacks.rs b/components/chainhook-cli/src/scan/stacks.rs index f4d0457c0..2b88b1efc 100644 --- a/components/chainhook-cli/src/scan/stacks.rs +++ b/components/chainhook-cli/src/scan/stacks.rs @@ -21,9 +21,9 @@ use chainhook_sdk::{ utils::Context, }; use chainhook_sdk::{ - chainhooks::{ - stacks::{handle_stacks_hook_action, StacksChainhookOccurrence, StacksTriggerChainhook}, - types::StacksChainhookInstance, + chainhooks::stacks::{ + handle_stacks_hook_action, StacksChainhookInstance, StacksChainhookOccurrence, + StacksTriggerChainhook, }, utils::{file_append, send_request, AbstractStacksBlock}, }; diff --git a/components/chainhook-cli/src/service/runloops.rs b/components/chainhook-cli/src/service/runloops.rs index 182a6087d..2d3d89557 100644 --- a/components/chainhook-cli/src/service/runloops.rs +++ b/components/chainhook-cli/src/service/runloops.rs @@ -1,8 +1,9 @@ use std::sync::mpsc::Sender; use chainhook_sdk::{ - chainhooks::types::{ - BitcoinChainhookInstance, ChainhookInstance, StacksChainhookInstance, + chainhooks::{ + bitcoin::BitcoinChainhookInstance, stacks::StacksChainhookInstance, + types::ChainhookInstance, }, observer::ObserverCommand, utils::Context, @@ -23,10 +24,7 @@ use super::ScanningData; pub fn start_stacks_scan_runloop( config: &Config, - stacks_scan_op_rx: crossbeam_channel::Receiver<( - StacksChainhookInstance, - Option, - )>, + stacks_scan_op_rx: crossbeam_channel::Receiver<(StacksChainhookInstance, Option)>, observer_command_tx: Sender, ctx: &Context, ) { diff --git a/components/chainhook-cli/src/service/tests/helpers/mock_service.rs b/components/chainhook-cli/src/service/tests/helpers/mock_service.rs index bae920966..e26e4ba1c 100644 --- a/components/chainhook-cli/src/service/tests/helpers/mock_service.rs +++ b/components/chainhook-cli/src/service/tests/helpers/mock_service.rs @@ -8,9 +8,8 @@ use crate::service::{ PredicateStatus, Service, }; use chainhook_sdk::{ - chainhooks::types::{ - ChainhookInstance, ChainhookSpecificationNetworkMap, StacksChainhookSpecificationNetworkMap, - }, + chainhooks::stacks::StacksChainhookSpecificationNetworkMap, + chainhooks::types::{ChainhookInstance, ChainhookSpecificationNetworkMap}, indexer::IndexerConfig, observer::ObserverCommand, types::{BitcoinBlockSignaling, BitcoinNetwork, Chain, StacksNetwork, StacksNodeConfig}, @@ -426,7 +425,7 @@ pub async fn setup_stacks_chainhook_test( panic!("test failed with error: {e}"); }); let stacks_spec = predicate - .into_selected_network_specification(&StacksNetwork::Devnet) + .into_specification_from_network(&StacksNetwork::Devnet) .unwrap_or_else(|e| { flush_redis(redis_port); redis_process.kill().unwrap(); diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs index 7c59ec94d..89e0f6e2f 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs @@ -1,15 +1,12 @@ -use super::types::{ - BitcoinChainhookInstance, BitcoinPredicateType, DescriptorMatchingRule, ExactMatchingRule, - HookAction, InputPredicate, MatchingRule, OrdinalOperations, OrdinalsMetaProtocol, - OutputPredicate, StacksOperations, -}; +use super::types::{ChainhookInstance, ExactMatchingRule, HookAction, MatchingRule}; use crate::utils::Context; use bitcoincore_rpc_json::bitcoin::{address::Payload, Address}; use chainhook_types::{ - BitcoinBlockData, BitcoinChainEvent, BitcoinTransactionData, BlockIdentifier, + BitcoinBlockData, BitcoinChainEvent, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, StacksBaseChainOperation, TransactionIdentifier, }; +use schemars::JsonSchema; use hiro_system_kit::slog; @@ -17,9 +14,10 @@ use miniscript::bitcoin::secp256k1::Secp256k1; use miniscript::Descriptor; use reqwest::{Client, Method}; +use serde::{de, Deserialize, Deserializer}; use serde_json::Value as JsonValue; use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, HashMap, HashSet}, str::FromStr, }; @@ -27,6 +25,126 @@ use reqwest::RequestBuilder; use hex::FromHex; +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct BitcoinChainhookSpecification { + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub end_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub expire_after_occurrence: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_proof: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_inputs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_outputs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_witness: Option, + #[serde(rename = "if_this")] + pub predicate: BitcoinPredicateType, + #[serde(rename = "then_that")] + pub action: HookAction, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct BitcoinChainhookSpecificationNetworkMap { + pub uuid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_uuid: Option, + pub name: String, + pub version: u32, + pub networks: BTreeMap, +} + +impl BitcoinChainhookSpecificationNetworkMap { + pub fn into_specification_for_network( + mut self, + network: &BitcoinNetwork, + ) -> Result { + let spec = self + .networks + .remove(network) + .ok_or("Network unknown".to_string())?; + Ok(BitcoinChainhookInstance { + uuid: self.uuid, + owner_uuid: self.owner_uuid, + name: self.name, + network: network.clone(), + version: self.version, + start_block: spec.start_block, + end_block: spec.end_block, + blocks: spec.blocks, + expire_after_occurrence: spec.expire_after_occurrence, + predicate: spec.predicate, + action: spec.action, + include_proof: spec.include_proof.unwrap_or(false), + include_inputs: spec.include_inputs.unwrap_or(false), + include_outputs: spec.include_outputs.unwrap_or(false), + include_witness: spec.include_witness.unwrap_or(false), + enabled: false, + expired_at: None, + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct BitcoinChainhookInstance { + pub uuid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_uuid: Option, + pub name: String, + pub network: BitcoinNetwork, + pub version: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub end_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub expire_after_occurrence: Option, + pub predicate: BitcoinPredicateType, + pub action: HookAction, + pub include_proof: bool, + pub include_inputs: bool, + pub include_outputs: bool, + pub include_witness: bool, + pub enabled: bool, + pub expired_at: Option, +} + +impl BitcoinChainhookInstance { + pub fn key(&self) -> String { + ChainhookInstance::bitcoin_key(&self.uuid) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct BitcoinTransactionFilterPredicate { + pub predicate: BitcoinPredicateType, +} + +impl BitcoinTransactionFilterPredicate { + pub fn new(predicate: BitcoinPredicateType) -> BitcoinTransactionFilterPredicate { + BitcoinTransactionFilterPredicate { predicate } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case", tag = "scope")] +pub enum BitcoinPredicateType { + Block, + Txid(ExactMatchingRule), + Inputs(InputPredicate), + Outputs(OutputPredicate), + StacksProtocol(StacksOperations), + OrdinalsProtocol(OrdinalOperations), +} + pub struct BitcoinTriggerChainhook<'a> { pub chainhook: &'a BitcoinChainhookInstance, pub apply: Vec<(Vec<&'a BitcoinTransactionData>, &'a BitcoinBlockData)>, @@ -44,6 +162,177 @@ pub struct BitcoinChainhookPayload { pub uuid: String, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum InputPredicate { + Txid(TxinPredicate), + WitnessScript(MatchingRule), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum OutputPredicate { + OpReturn(MatchingRule), + P2pkh(ExactMatchingRule), + P2sh(ExactMatchingRule), + P2wpkh(ExactMatchingRule), + P2wsh(ExactMatchingRule), + Descriptor(DescriptorMatchingRule), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case", tag = "operation")] +pub enum StacksOperations { + StackerRewarded, + BlockCommitted, + LeaderRegistered, + StxTransferred, + StxLocked, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "kebab-case")] +pub enum OrdinalsMetaProtocol { + All, + #[serde(rename = "brc-20")] + Brc20, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +pub struct InscriptionFeedData { + #[serde(skip_serializing_if = "Option::is_none")] + pub meta_protocols: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case", tag = "operation")] +pub enum OrdinalOperations { + InscriptionFeed(InscriptionFeedData), +} + +pub fn get_stacks_canonical_magic_bytes(network: &BitcoinNetwork) -> [u8; 2] { + match network { + BitcoinNetwork::Mainnet => *b"X2", + BitcoinNetwork::Testnet => *b"T2", + BitcoinNetwork::Regtest => *b"id", + BitcoinNetwork::Signet => unreachable!(), + } +} + +pub struct PoxConfig { + pub genesis_block_height: u64, + pub prepare_phase_len: u64, + pub reward_phase_len: u64, + pub rewarded_addresses_per_block: usize, +} + +impl PoxConfig { + pub fn get_pox_cycle_len(&self) -> u64 { + self.prepare_phase_len + self.reward_phase_len + } + + pub fn get_pox_cycle_id(&self, block_height: u64) -> u64 { + (block_height.saturating_sub(self.genesis_block_height)) / self.get_pox_cycle_len() + } + + pub fn get_pos_in_pox_cycle(&self, block_height: u64) -> u64 { + (block_height.saturating_sub(self.genesis_block_height)) % self.get_pox_cycle_len() + } + + pub fn get_burn_address(&self) -> &str { + match self.genesis_block_height { + 666050 => "1111111111111111111114oLvT2", + 2000000 => "burn-address-regtest", + _ => "burn-address", + } + } +} +const POX_CONFIG_MAINNET: PoxConfig = PoxConfig { + genesis_block_height: 666050, + prepare_phase_len: 100, + reward_phase_len: 2100, + rewarded_addresses_per_block: 2, +}; + +const POX_CONFIG_TESTNET: PoxConfig = PoxConfig { + genesis_block_height: 2000000, + prepare_phase_len: 50, + reward_phase_len: 1050, + rewarded_addresses_per_block: 2, +}; + +const POX_CONFIG_DEVNET: PoxConfig = PoxConfig { + genesis_block_height: 100, + prepare_phase_len: 4, + reward_phase_len: 10, + rewarded_addresses_per_block: 2, +}; + +pub fn get_canonical_pox_config(network: &BitcoinNetwork) -> PoxConfig { + match network { + BitcoinNetwork::Mainnet => POX_CONFIG_MAINNET, + BitcoinNetwork::Testnet => POX_CONFIG_TESTNET, + BitcoinNetwork::Regtest => POX_CONFIG_DEVNET, + BitcoinNetwork::Signet => unreachable!(), + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(u8)] +pub enum StacksOpcodes { + BlockCommit = '[' as u8, + KeyRegister = '^' as u8, + StackStx = 'x' as u8, + PreStx = 'p' as u8, + TransferStx = '$' as u8, +} + +impl TryFrom for StacksOpcodes { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == StacksOpcodes::BlockCommit as u8 => Ok(StacksOpcodes::BlockCommit), + x if x == StacksOpcodes::KeyRegister as u8 => Ok(StacksOpcodes::KeyRegister), + x if x == StacksOpcodes::StackStx as u8 => Ok(StacksOpcodes::StackStx), + x if x == StacksOpcodes::PreStx as u8 => Ok(StacksOpcodes::PreStx), + x if x == StacksOpcodes::TransferStx as u8 => Ok(StacksOpcodes::TransferStx), + _ => Err(()), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct TxinPredicate { + pub txid: String, + pub vout: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct DescriptorMatchingRule { + // expression defines the bitcoin descriptor. + pub expression: String, + #[serde(default, deserialize_with = "deserialize_descriptor_range")] + pub range: Option<[u32; 2]>, +} + +// deserialize_descriptor_range makes sure that the range value is valid. +fn deserialize_descriptor_range<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let range: [u32; 2] = Deserialize::deserialize(deserializer)?; + if !(range[0] < range[1]) { + Err(de::Error::custom( + "First element of 'range' must be lower than the second element", + )) + } else { + Ok(Some(range)) + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BitcoinChainhookOccurrencePayload { pub apply: Vec, diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs index 3ab79166f..dd9cb52b2 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use super::super::types::MatchingRule; use super::*; -use crate::chainhooks::types::InscriptionFeedData; +use crate::chainhooks::bitcoin::InscriptionFeedData; use crate::indexer::tests::helpers::accounts; use crate::indexer::tests::helpers::bitcoin_blocks::generate_test_bitcoin_block; use crate::indexer::tests::helpers::transactions::generate_test_tx_bitcoin_p2pkh_transfer; diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index 593fd68ea..d6991f4cf 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -1,16 +1,15 @@ use crate::utils::{AbstractStacksBlock, Context}; -use super::types::{ - BlockIdentifierIndexRule, ExactMatchingRule, HookAction, StacksChainhookInstance, - StacksContractDeploymentPredicate, StacksPredicate, StacksPrintEventBasedPredicate, -}; +use super::types::{BlockIdentifierIndexRule, ChainhookInstance, ExactMatchingRule, HookAction}; use chainhook_types::{ - BlockIdentifier, StacksChainEvent, StacksTransactionData, StacksTransactionEvent, - StacksTransactionEventPayload, StacksTransactionKind, TransactionIdentifier, + BlockIdentifier, StacksChainEvent, StacksNetwork, StacksTransactionData, + StacksTransactionEvent, StacksTransactionEventPayload, StacksTransactionKind, + TransactionIdentifier, }; use hiro_system_kit::slog; use regex::Regex; use reqwest::{Client, Method}; +use schemars::JsonSchema; use serde_json::Value as JsonValue; use stacks_codec::clarity::codec::StacksMessageCodec; use stacks_codec::clarity::vm::types::{CharType, SequenceData, Value as ClarityValue}; @@ -19,6 +18,183 @@ use std::io::Cursor; use reqwest::RequestBuilder; +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct StacksChainhookSpecification { + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub end_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub expire_after_occurrence: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub capture_all_events: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub decode_clarity_values: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_contract_abi: Option, + #[serde(rename = "if_this")] + pub predicate: StacksPredicate, + #[serde(rename = "then_that")] + pub action: HookAction, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +pub struct StacksChainhookSpecificationNetworkMap { + pub uuid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_uuid: Option, + pub name: String, + pub version: u32, + pub networks: BTreeMap, +} + +impl StacksChainhookSpecificationNetworkMap { + pub fn into_specification_from_network( + mut self, + network: &StacksNetwork, + ) -> Result { + let spec = self + .networks + .remove(network) + .ok_or("Network unknown".to_string())?; + Ok(StacksChainhookInstance { + uuid: self.uuid, + owner_uuid: self.owner_uuid, + name: self.name, + network: network.clone(), + version: self.version, + start_block: spec.start_block, + end_block: spec.end_block, + blocks: spec.blocks, + capture_all_events: spec.capture_all_events, + decode_clarity_values: spec.decode_clarity_values, + expire_after_occurrence: spec.expire_after_occurrence, + include_contract_abi: spec.include_contract_abi, + predicate: spec.predicate, + action: spec.action, + enabled: false, + expired_at: None, + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct StacksChainhookInstance { + pub uuid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_uuid: Option, + pub name: String, + pub network: StacksNetwork, + pub version: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub end_block: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub expire_after_occurrence: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub capture_all_events: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub decode_clarity_values: Option, + pub include_contract_abi: Option, + #[serde(rename = "predicate")] + pub predicate: StacksPredicate, + pub action: HookAction, + pub enabled: bool, + pub expired_at: Option, +} + +impl StacksChainhookInstance { + pub fn key(&self) -> String { + ChainhookInstance::stacks_key(&self.uuid) + } + + pub fn is_predicate_targeting_block_header(&self) -> bool { + match &self.predicate { + StacksPredicate::BlockHeight(_) + // | &StacksPredicate::BitcoinBlockHeight(_) + => true, + _ => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "scope")] +pub enum StacksPredicate { + BlockHeight(BlockIdentifierIndexRule), + ContractDeployment(StacksContractDeploymentPredicate), + ContractCall(StacksContractCallBasedPredicate), + PrintEvent(StacksPrintEventBasedPredicate), + FtEvent(StacksFtEventBasedPredicate), + NftEvent(StacksNftEventBasedPredicate), + StxEvent(StacksStxEventBasedPredicate), + Txid(ExactMatchingRule), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StacksContractCallBasedPredicate { + pub contract_identifier: String, + pub method: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +// #[serde(tag = "type", content = "rule")] +pub enum StacksContractDeploymentPredicate { + Deployer(String), + ImplementTrait(StacksTrait), +} +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum StacksTrait { + Sip09, + Sip10, + #[serde(rename = "*")] + Any, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +#[serde(untagged)] +pub enum StacksPrintEventBasedPredicate { + Contains { + contract_identifier: String, + contains: String, + }, + MatchesRegex { + contract_identifier: String, + #[serde(rename = "matches_regex")] + regex: String, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StacksFtEventBasedPredicate { + pub asset_identifier: String, + pub actions: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StacksNftEventBasedPredicate { + pub asset_identifier: String, + pub actions: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StacksStxEventBasedPredicate { + pub actions: Vec, +} + #[derive(Clone)] pub struct StacksTriggerChainhook<'a> { pub chainhook: &'a StacksChainhookInstance, diff --git a/components/chainhook-sdk/src/chainhooks/tests/mod.rs b/components/chainhook-sdk/src/chainhooks/tests/mod.rs index 91e08bb90..a503d9340 100644 --- a/components/chainhook-sdk/src/chainhooks/tests/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/tests/mod.rs @@ -5,20 +5,18 @@ use self::fixtures::get_all_event_payload_types; use super::{ stacks::{ evaluate_stacks_chainhooks_on_chain_event, handle_stacks_hook_action, - StacksChainhookOccurrence, StacksTriggerChainhook, - }, - types::{ - ExactMatchingRule, FileHook, StacksChainhookInstance, - StacksContractCallBasedPredicate, StacksContractDeploymentPredicate, - StacksFtEventBasedPredicate, StacksNftEventBasedPredicate, StacksPrintEventBasedPredicate, - StacksTrait, + StacksChainhookInstance, StacksChainhookOccurrence, StacksContractCallBasedPredicate, + StacksContractDeploymentPredicate, StacksFtEventBasedPredicate, + StacksNftEventBasedPredicate, StacksPredicate, StacksPrintEventBasedPredicate, + StacksStxEventBasedPredicate, StacksTrait, StacksTriggerChainhook, }, + types::{ExactMatchingRule, FileHook}, }; use crate::{chainhooks::stacks::serialize_stacks_payload_to_json, utils::Context}; use crate::{ chainhooks::{ tests::fixtures::{get_expected_occurrence, get_test_event_payload_by_type}, - types::{HookAction, StacksPredicate, StacksStxEventBasedPredicate}, + types::HookAction, }, utils::AbstractStacksBlock, }; diff --git a/components/chainhook-sdk/src/chainhooks/types.rs b/components/chainhook-sdk/src/chainhooks/types.rs index 24863fe40..173918a06 100644 --- a/components/chainhook-sdk/src/chainhooks/types.rs +++ b/components/chainhook-sdk/src/chainhooks/types.rs @@ -1,14 +1,17 @@ -use std::collections::{BTreeMap, HashSet}; - use chainhook_types::{BitcoinNetwork, StacksNetwork}; use reqwest::Url; use serde::ser::{SerializeSeq, Serializer}; -use serde::{de, Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use schemars::JsonSchema; use crate::utils::MAX_BLOCK_HEIGHTS_ENTRIES; +use crate::chainhooks::bitcoin::BitcoinChainhookInstance; +use crate::chainhooks::bitcoin::BitcoinChainhookSpecificationNetworkMap; +use crate::chainhooks::stacks::StacksChainhookInstance; +use crate::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; + #[derive(Deserialize, Debug, Clone)] pub struct ChainhookConfig { pub stacks_chainhooks: Vec, @@ -30,12 +33,12 @@ impl ChainhookConfig { ) -> Result { let spec = match hook { ChainhookSpecificationNetworkMap::Stacks(hook) => { - let spec = hook.into_selected_network_specification(networks.1)?; + let spec = hook.into_specification_from_network(networks.1)?; self.stacks_chainhooks.push(spec.clone()); ChainhookInstance::Stacks(spec) } ChainhookSpecificationNetworkMap::Bitcoin(hook) => { - let spec = hook.into_selected_network_specification(networks.0)?; + let spec = hook.into_specification_for_network(networks.0)?; self.bitcoin_chainhooks.push(spec.clone()); ChainhookInstance::Bitcoin(spec) } @@ -193,38 +196,6 @@ impl ChainhookInstance { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct BitcoinChainhookInstance { - pub uuid: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner_uuid: Option, - pub name: String, - pub network: BitcoinNetwork, - pub version: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub blocks: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expire_after_occurrence: Option, - pub predicate: BitcoinPredicateType, - pub action: HookAction, - pub include_proof: bool, - pub include_inputs: bool, - pub include_outputs: bool, - pub include_witness: bool, - pub enabled: bool, - pub expired_at: Option, -} - -impl BitcoinChainhookInstance { - pub fn key(&self) -> String { - ChainhookInstance::bitcoin_key(&self.uuid) - } -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case", tag = "chain")] pub enum ChainhookSpecificationNetworkMap { @@ -290,133 +261,6 @@ impl ChainhookSpecificationNetworkMap { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct BitcoinChainhookSpecificationNetworkMap { - pub uuid: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner_uuid: Option, - pub name: String, - pub version: u32, - pub networks: BTreeMap, -} - -impl BitcoinChainhookSpecificationNetworkMap { - pub fn into_selected_network_specification( - mut self, - network: &BitcoinNetwork, - ) -> Result { - let spec = self - .networks - .remove(network) - .ok_or("Network unknown".to_string())?; - Ok(BitcoinChainhookInstance { - uuid: self.uuid, - owner_uuid: self.owner_uuid, - name: self.name, - network: network.clone(), - version: self.version, - start_block: spec.start_block, - end_block: spec.end_block, - blocks: spec.blocks, - expire_after_occurrence: spec.expire_after_occurrence, - predicate: spec.predicate, - action: spec.action, - include_proof: spec.include_proof.unwrap_or(false), - include_inputs: spec.include_inputs.unwrap_or(false), - include_outputs: spec.include_outputs.unwrap_or(false), - include_witness: spec.include_witness.unwrap_or(false), - enabled: false, - expired_at: None, - }) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct BitcoinChainhookSpecification { - #[serde(skip_serializing_if = "Option::is_none")] - pub blocks: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expire_after_occurrence: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_proof: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_inputs: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_outputs: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_witness: Option, - #[serde(rename = "if_this")] - pub predicate: BitcoinPredicateType, - #[serde(rename = "then_that")] - pub action: HookAction, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct StacksChainhookSpecificationNetworkMap { - pub uuid: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner_uuid: Option, - pub name: String, - pub version: u32, - pub networks: BTreeMap, -} - -impl StacksChainhookSpecificationNetworkMap { - pub fn into_selected_network_specification( - mut self, - network: &StacksNetwork, - ) -> Result { - let spec = self - .networks - .remove(network) - .ok_or("Network unknown".to_string())?; - Ok(StacksChainhookInstance { - uuid: self.uuid, - owner_uuid: self.owner_uuid, - name: self.name, - network: network.clone(), - version: self.version, - start_block: spec.start_block, - end_block: spec.end_block, - blocks: spec.blocks, - capture_all_events: spec.capture_all_events, - decode_clarity_values: spec.decode_clarity_values, - expire_after_occurrence: spec.expire_after_occurrence, - include_contract_abi: spec.include_contract_abi, - predicate: spec.predicate, - action: spec.action, - enabled: false, - expired_at: None, - }) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -pub struct StacksChainhookSpecification { - #[serde(skip_serializing_if = "Option::is_none")] - pub blocks: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expire_after_occurrence: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub capture_all_events: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub decode_clarity_values: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_contract_abi: Option, - #[serde(rename = "if_this")] - pub predicate: StacksPredicate, - #[serde(rename = "then_that")] - pub action: HookAction, -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum HookAction { @@ -451,7 +295,7 @@ pub struct HttpHook { pub struct FileHook { pub path: String, } - +// todo: can we remove this struct? #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] pub struct ScriptTemplate { pub instructions: Vec, @@ -496,177 +340,6 @@ impl ScriptTemplate { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct BitcoinTransactionFilterPredicate { - pub predicate: BitcoinPredicateType, -} - -impl BitcoinTransactionFilterPredicate { - pub fn new(predicate: BitcoinPredicateType) -> BitcoinTransactionFilterPredicate { - BitcoinTransactionFilterPredicate { predicate } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case", tag = "scope")] -pub enum BitcoinPredicateType { - Block, - Txid(ExactMatchingRule), - Inputs(InputPredicate), - Outputs(OutputPredicate), - StacksProtocol(StacksOperations), - OrdinalsProtocol(OrdinalOperations), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum InputPredicate { - Txid(TxinPredicate), - WitnessScript(MatchingRule), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum OutputPredicate { - OpReturn(MatchingRule), - P2pkh(ExactMatchingRule), - P2sh(ExactMatchingRule), - P2wpkh(ExactMatchingRule), - P2wsh(ExactMatchingRule), - Descriptor(DescriptorMatchingRule), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case", tag = "operation")] -pub enum StacksOperations { - StackerRewarded, - BlockCommitted, - LeaderRegistered, - StxTransferred, - StxLocked, -} - -#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "kebab-case")] -pub enum OrdinalsMetaProtocol { - All, - #[serde(rename = "brc-20")] - Brc20, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -pub struct InscriptionFeedData { - #[serde(skip_serializing_if = "Option::is_none")] - pub meta_protocols: Option>, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case", tag = "operation")] -pub enum OrdinalOperations { - InscriptionFeed(InscriptionFeedData), -} - -pub fn get_stacks_canonical_magic_bytes(network: &BitcoinNetwork) -> [u8; 2] { - match network { - BitcoinNetwork::Mainnet => *b"X2", - BitcoinNetwork::Testnet => *b"T2", - BitcoinNetwork::Regtest => *b"id", - BitcoinNetwork::Signet => unreachable!(), - } -} - -pub struct PoxConfig { - pub genesis_block_height: u64, - pub prepare_phase_len: u64, - pub reward_phase_len: u64, - pub rewarded_addresses_per_block: usize, -} - -impl PoxConfig { - pub fn get_pox_cycle_len(&self) -> u64 { - self.prepare_phase_len + self.reward_phase_len - } - - pub fn get_pox_cycle_id(&self, block_height: u64) -> u64 { - (block_height.saturating_sub(self.genesis_block_height)) / self.get_pox_cycle_len() - } - - pub fn get_pos_in_pox_cycle(&self, block_height: u64) -> u64 { - (block_height.saturating_sub(self.genesis_block_height)) % self.get_pox_cycle_len() - } - - pub fn get_burn_address(&self) -> &str { - match self.genesis_block_height { - 666050 => "1111111111111111111114oLvT2", - 2000000 => "burn-address-regtest", - _ => "burn-address", - } - } -} - -const POX_CONFIG_MAINNET: PoxConfig = PoxConfig { - genesis_block_height: 666050, - prepare_phase_len: 100, - reward_phase_len: 2100, - rewarded_addresses_per_block: 2, -}; - -const POX_CONFIG_TESTNET: PoxConfig = PoxConfig { - genesis_block_height: 2000000, - prepare_phase_len: 50, - reward_phase_len: 1050, - rewarded_addresses_per_block: 2, -}; - -const POX_CONFIG_DEVNET: PoxConfig = PoxConfig { - genesis_block_height: 100, - prepare_phase_len: 4, - reward_phase_len: 10, - rewarded_addresses_per_block: 2, -}; - -pub fn get_canonical_pox_config(network: &BitcoinNetwork) -> PoxConfig { - match network { - BitcoinNetwork::Mainnet => POX_CONFIG_MAINNET, - BitcoinNetwork::Testnet => POX_CONFIG_TESTNET, - BitcoinNetwork::Regtest => POX_CONFIG_DEVNET, - BitcoinNetwork::Signet => unreachable!(), - } -} - -#[derive(Debug, Clone, PartialEq)] -#[repr(u8)] -pub enum StacksOpcodes { - BlockCommit = '[' as u8, - KeyRegister = '^' as u8, - StackStx = 'x' as u8, - PreStx = 'p' as u8, - TransferStx = '$' as u8, -} - -impl TryFrom for StacksOpcodes { - type Error = (); - - fn try_from(v: u8) -> Result { - match v { - x if x == StacksOpcodes::BlockCommit as u8 => Ok(StacksOpcodes::BlockCommit), - x if x == StacksOpcodes::KeyRegister as u8 => Ok(StacksOpcodes::KeyRegister), - x if x == StacksOpcodes::StackStx as u8 => Ok(StacksOpcodes::StackStx), - x if x == StacksOpcodes::PreStx as u8 => Ok(StacksOpcodes::PreStx), - x if x == StacksOpcodes::TransferStx as u8 => Ok(StacksOpcodes::TransferStx), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct TxinPredicate { - pub txid: String, - pub vout: u32, -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum BlockIdentifierIndexRule { @@ -697,30 +370,6 @@ pub enum ExactMatchingRule { Equals(String), } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct DescriptorMatchingRule { - // expression defines the bitcoin descriptor. - pub expression: String, - #[serde(default, deserialize_with = "deserialize_descriptor_range")] - pub range: Option<[u32; 2]>, -} - -// deserialize_descriptor_range makes sure that the range value is valid. -fn deserialize_descriptor_range<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let range: [u32; 2] = Deserialize::deserialize(deserializer)?; - if !(range[0] < range[1]) { - Err(de::Error::custom( - "First element of 'range' must be lower than the second element", - )) - } else { - Ok(Some(range)) - } -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum BlockIdentifierHashRule { @@ -728,121 +377,6 @@ pub enum BlockIdentifierHashRule { BuildsOff(String), } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct StacksChainhookInstance { - pub uuid: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner_uuid: Option, - pub name: String, - pub network: StacksNetwork, - pub version: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub blocks: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub end_block: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expire_after_occurrence: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub capture_all_events: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub decode_clarity_values: Option, - pub include_contract_abi: Option, - #[serde(rename = "predicate")] - pub predicate: StacksPredicate, - pub action: HookAction, - pub enabled: bool, - pub expired_at: Option, -} - -impl StacksChainhookInstance { - pub fn key(&self) -> String { - ChainhookInstance::stacks_key(&self.uuid) - } - - pub fn is_predicate_targeting_block_header(&self) -> bool { - match &self.predicate { - StacksPredicate::BlockHeight(_) - // | &StacksPredicate::BitcoinBlockHeight(_) - => true, - _ => false, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -#[serde(tag = "scope")] -pub enum StacksPredicate { - BlockHeight(BlockIdentifierIndexRule), - ContractDeployment(StacksContractDeploymentPredicate), - ContractCall(StacksContractCallBasedPredicate), - PrintEvent(StacksPrintEventBasedPredicate), - FtEvent(StacksFtEventBasedPredicate), - NftEvent(StacksNftEventBasedPredicate), - StxEvent(StacksStxEventBasedPredicate), - Txid(ExactMatchingRule), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct StacksContractCallBasedPredicate { - pub contract_identifier: String, - pub method: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -// #[serde(tag = "type", content = "rule")] -pub enum StacksContractDeploymentPredicate { - Deployer(String), - ImplementTrait(StacksTrait), -} -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum StacksTrait { - Sip09, - Sip10, - #[serde(rename = "*")] - Any, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -#[serde(untagged)] -pub enum StacksPrintEventBasedPredicate { - Contains { - contract_identifier: String, - contains: String, - }, - MatchesRegex { - contract_identifier: String, - #[serde(rename = "matches_regex")] - regex: String, - }, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct StacksFtEventBasedPredicate { - pub asset_identifier: String, - pub actions: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct StacksNftEventBasedPredicate { - pub asset_identifier: String, - pub actions: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct StacksStxEventBasedPredicate { - pub actions: Vec, -} - pub fn opcode_to_hex(asm: &str) -> Option { match asm { "OP_PUSHBYTES_0" => Some(0x00), diff --git a/components/chainhook-sdk/src/indexer/bitcoin/mod.rs b/components/chainhook-sdk/src/indexer/bitcoin/mod.rs index 65889dbbf..2af20ea63 100644 --- a/components/chainhook-sdk/src/indexer/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/indexer/bitcoin/mod.rs @@ -1,9 +1,8 @@ use std::time::Duration; -use crate::chainhooks::types::{ +use crate::chainhooks::bitcoin::{ get_canonical_pox_config, get_stacks_canonical_magic_bytes, PoxConfig, StacksOpcodes, }; - use crate::observer::BitcoinConfig; use crate::utils::Context; use bitcoincore_rpc::bitcoin::hashes::Hash; diff --git a/components/chainhook-sdk/src/observer/tests/mod.rs b/components/chainhook-sdk/src/observer/tests/mod.rs index 95f4b5351..41cbd56a2 100644 --- a/components/chainhook-sdk/src/observer/tests/mod.rs +++ b/components/chainhook-sdk/src/observer/tests/mod.rs @@ -1,10 +1,18 @@ +use crate::chainhooks::bitcoin::BitcoinChainhookInstance; +use crate::chainhooks::bitcoin::BitcoinChainhookSpecification; +use crate::chainhooks::bitcoin::BitcoinChainhookSpecificationNetworkMap; +use crate::chainhooks::bitcoin::BitcoinPredicateType; +use crate::chainhooks::bitcoin::InscriptionFeedData; +use crate::chainhooks::bitcoin::OrdinalOperations; +use crate::chainhooks::bitcoin::OutputPredicate; +use crate::chainhooks::stacks::StacksChainhookInstance; +use crate::chainhooks::stacks::StacksChainhookSpecification; +use crate::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; +use crate::chainhooks::stacks::StacksContractCallBasedPredicate; +use crate::chainhooks::stacks::StacksPredicate; use crate::chainhooks::types::{ - BitcoinChainhookInstance, BitcoinChainhookSpecification, - BitcoinChainhookSpecificationNetworkMap, BitcoinPredicateType, ChainhookConfig, - ChainhookInstance, ChainhookSpecificationNetworkMap, ExactMatchingRule, HookAction, - InscriptionFeedData, OrdinalOperations, OutputPredicate, StacksChainhookInstance, - StacksChainhookSpecification, StacksChainhookSpecificationNetworkMap, - StacksContractCallBasedPredicate, StacksPredicate, + ChainhookConfig, ChainhookInstance, ChainhookSpecificationNetworkMap, ExactMatchingRule, + HookAction, }; use crate::indexer::fork_scratch_pad::ForkScratchPad; use crate::indexer::tests::helpers::transactions::generate_test_tx_bitcoin_p2pkh_transfer; @@ -163,7 +171,7 @@ fn generate_and_register_new_stacks_chainhook( ChainhookSpecificationNetworkMap::Stacks(chainhook.clone()), )); let mut chainhook = chainhook - .into_selected_network_specification(&StacksNetwork::Devnet) + .into_specification_from_network(&StacksNetwork::Devnet) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( @@ -207,7 +215,7 @@ fn generate_and_register_new_bitcoin_chainhook( ChainhookSpecificationNetworkMap::Bitcoin(chainhook.clone()), )); let mut chainhook = chainhook - .into_selected_network_specification(&BitcoinNetwork::Regtest) + .into_specification_for_network(&BitcoinNetwork::Regtest) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( @@ -326,7 +334,7 @@ fn generate_and_register_new_ordinals_chainhook( ChainhookSpecificationNetworkMap::Bitcoin(chainhook.clone()), )); let mut chainhook = chainhook - .into_selected_network_specification(&BitcoinNetwork::Regtest) + .into_specification_for_network(&BitcoinNetwork::Regtest) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate( @@ -582,7 +590,7 @@ fn test_stacks_chainhook_auto_deregister() { ChainhookSpecificationNetworkMap::Stacks(chainhook.clone()), )); let mut chainhook = chainhook - .into_selected_network_specification(&StacksNetwork::Devnet) + .into_specification_from_network(&StacksNetwork::Devnet) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( From 4debc28103762f94c5eef9cb15cdfedd6d0c32ed Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 13:13:18 -0400 Subject: [PATCH 15/28] add helpers for registering chainhooks --- components/chainhook-sdk/src/observer/mod.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 7d7d89cd9..b4149fb68 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -4,10 +4,11 @@ mod zmq; use crate::chainhooks::bitcoin::{ evaluate_bitcoin_chainhooks_on_chain_event, handle_bitcoin_hook_action, - BitcoinChainhookOccurrence, BitcoinChainhookOccurrencePayload, BitcoinTriggerChainhook, + BitcoinChainhookInstance, BitcoinChainhookOccurrence, BitcoinChainhookOccurrencePayload, + BitcoinTriggerChainhook, }; use crate::chainhooks::stacks::{ - evaluate_stacks_chainhooks_on_chain_event, handle_stacks_hook_action, + evaluate_stacks_chainhooks_on_chain_event, handle_stacks_hook_action, StacksChainhookInstance, StacksChainhookOccurrence, StacksChainhookOccurrencePayload, }; use crate::chainhooks::types::{ @@ -334,6 +335,20 @@ impl EventObserverConfig { Ok(()) } + pub fn register_bitcoin_chainhook_instance( + &mut self, + spec: BitcoinChainhookInstance, + ) -> Result<(), String> { + self.register_chainhook_instance(ChainhookInstance::Bitcoin(spec)) + } + + pub fn register_stacks_chainhook_instance( + &mut self, + spec: StacksChainhookInstance, + ) -> Result<(), String> { + self.register_chainhook_instance(ChainhookInstance::Stacks(spec)) + } + pub fn get_bitcoin_config(&self) -> BitcoinConfig { let bitcoin_config = BitcoinConfig { username: self.bitcoind_rpc_username.clone(), From c54b6e7f171f09a3a058b397b4131fcf8d9dca7e Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 13:23:37 -0400 Subject: [PATCH 16/28] rename ChainhookConfig -> ChainhookStore remove unnecessary wrapper around ChainhookStore --- components/chainhook-cli/src/config/mod.rs | 3 +- components/chainhook-cli/src/service/mod.rs | 10 +- .../src/service/tests/observer_tests.rs | 3 +- .../chainhook-sdk/src/chainhooks/types.rs | 10 +- components/chainhook-sdk/src/observer/mod.rs | 128 +++++------------- .../chainhook-sdk/src/observer/tests/mod.rs | 11 +- 6 files changed, 55 insertions(+), 110 deletions(-) diff --git a/components/chainhook-cli/src/config/mod.rs b/components/chainhook-cli/src/config/mod.rs index 1457dade2..7c3c1f8eb 100644 --- a/components/chainhook-cli/src/config/mod.rs +++ b/components/chainhook-cli/src/config/mod.rs @@ -1,6 +1,7 @@ pub mod file; pub mod generator; +use chainhook_sdk::chainhooks::types::ChainhookStore; pub use chainhook_sdk::indexer::IndexerConfig; use chainhook_sdk::observer::EventObserverConfig; use chainhook_sdk::types::{ @@ -114,7 +115,7 @@ impl Config { pub fn get_event_observer_config(&self) -> EventObserverConfig { EventObserverConfig { bitcoin_rpc_proxy_enabled: true, - chainhook_config: None, + registered_chainhooks: ChainhookStore::new(), bitcoind_rpc_username: self.network.bitcoind_rpc_username.clone(), bitcoind_rpc_password: self.network.bitcoind_rpc_password.clone(), bitcoind_rpc_url: self.network.bitcoind_rpc_url.clone(), diff --git a/components/chainhook-cli/src/service/mod.rs b/components/chainhook-cli/src/service/mod.rs index 28784f162..165816201 100644 --- a/components/chainhook-cli/src/service/mod.rs +++ b/components/chainhook-cli/src/service/mod.rs @@ -11,7 +11,7 @@ use crate::storage::{ open_readwrite_stacks_db_conn, }; -use chainhook_sdk::chainhooks::types::{ChainhookConfig, ChainhookSpecificationNetworkMap}; +use chainhook_sdk::chainhooks::types::{ChainhookSpecificationNetworkMap, ChainhookStore}; use chainhook_sdk::chainhooks::types::ChainhookInstance; use chainhook_sdk::observer::{ @@ -42,7 +42,7 @@ impl Service { predicates_from_startup: Vec, observer_commands_tx_rx: Option<(Sender, Receiver)>, ) -> Result<(), String> { - let mut chainhook_config = ChainhookConfig::new(); + let mut chainhook_store = ChainhookStore::new(); // store all predicates from Redis that were in the process of scanning when // chainhook was shutdown - we need to resume where we left off @@ -87,7 +87,7 @@ impl Service { continue; } } - match chainhook_config.register_instance(predicate) { + match chainhook_store.register_instance(predicate) { Ok(_) => { debug!( self.ctx.expect_logger(), @@ -128,7 +128,7 @@ impl Service { } }; } - match chainhook_config.register_instance_from_network_map( + match chainhook_store.register_instance_from_network_map( ( &self.config.network.bitcoin_network, &self.config.network.stacks_network, @@ -159,7 +159,7 @@ impl Service { // let (ordinal_indexer_command_tx, ordinal_indexer_command_rx) = channel(); let mut event_observer_config = self.config.get_event_observer_config(); - event_observer_config.chainhook_config = Some(chainhook_config); + event_observer_config.registered_chainhooks = chainhook_store; // Download and ingest a Stacks dump if self.config.rely_on_remote_stacks_tsv() { diff --git a/components/chainhook-cli/src/service/tests/observer_tests.rs b/components/chainhook-cli/src/service/tests/observer_tests.rs index 3924bb5a2..cfb7699be 100644 --- a/components/chainhook-cli/src/service/tests/observer_tests.rs +++ b/components/chainhook-cli/src/service/tests/observer_tests.rs @@ -1,6 +1,7 @@ use std::{sync::mpsc::channel, thread::sleep, time::Duration}; use chainhook_sdk::{ + chainhooks::types::ChainhookStore, observer::{start_event_observer, EventObserverConfig}, types::{BitcoinNetwork, StacksNodeConfig}, utils::Context, @@ -188,7 +189,7 @@ async fn it_responds_200_for_unimplemented_endpoints( panic!("test failed with error: {e}"); }); let config = EventObserverConfig { - chainhook_config: None, + registered_chainhooks: ChainhookStore::new(), bitcoin_rpc_proxy_enabled: false, bitcoind_rpc_username: format!(""), bitcoind_rpc_password: format!(""), diff --git a/components/chainhook-sdk/src/chainhooks/types.rs b/components/chainhook-sdk/src/chainhooks/types.rs index 173918a06..c1d440478 100644 --- a/components/chainhook-sdk/src/chainhooks/types.rs +++ b/components/chainhook-sdk/src/chainhooks/types.rs @@ -13,14 +13,14 @@ use crate::chainhooks::stacks::StacksChainhookInstance; use crate::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; #[derive(Deserialize, Debug, Clone)] -pub struct ChainhookConfig { +pub struct ChainhookStore { pub stacks_chainhooks: Vec, pub bitcoin_chainhooks: Vec, } -impl ChainhookConfig { - pub fn new() -> ChainhookConfig { - ChainhookConfig { +impl ChainhookStore { + pub fn new() -> ChainhookStore { + ChainhookStore { stacks_chainhooks: vec![], bitcoin_chainhooks: vec![], } @@ -137,7 +137,7 @@ impl ChainhookConfig { } } -impl Serialize for ChainhookConfig { +impl Serialize for ChainhookStore { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index b4149fb68..a6affe56e 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -12,7 +12,7 @@ use crate::chainhooks::stacks::{ StacksChainhookOccurrence, StacksChainhookOccurrencePayload, }; use crate::chainhooks::types::{ - ChainhookConfig, ChainhookInstance, ChainhookSpecificationNetworkMap, + ChainhookInstance, ChainhookSpecificationNetworkMap, ChainhookStore, }; use crate::indexer::bitcoin::{ @@ -69,7 +69,7 @@ pub enum DataHandlerEvent { #[derive(Debug, Clone)] pub struct EventObserverConfig { - pub chainhook_config: Option, + pub registered_chainhooks: ChainhookStore, pub bitcoin_rpc_proxy_enabled: bool, pub bitcoind_rpc_username: String, pub bitcoind_rpc_password: String, @@ -278,7 +278,7 @@ impl BitcoinEventObserverBuilder { BitcoinNetwork::Regtest }; Ok(EventObserverConfig { - chainhook_config: None, + registered_chainhooks: ChainhookStore::new(), bitcoin_rpc_proxy_enabled: false, bitcoind_rpc_username: self .bitcoind_rpc_username @@ -308,7 +308,7 @@ impl BitcoinEventObserverBuilder { impl EventObserverConfig { pub fn default() -> Self { EventObserverConfig { - chainhook_config: None, + registered_chainhooks: ChainhookStore::new(), bitcoin_rpc_proxy_enabled: false, bitcoind_rpc_username: "devnet".into(), bitcoind_rpc_password: "devnet".into(), @@ -325,13 +325,10 @@ impl EventObserverConfig { } pub fn register_chainhook_instance(&mut self, spec: ChainhookInstance) -> Result<(), String> { - if let Some(ref mut chainhook_config) = self.chainhook_config.borrow_mut() { - chainhook_config.register_instance(spec)?; - } else { - let mut chainhook_config = ChainhookConfig::new(); - chainhook_config.register_instance(spec)?; - self.chainhook_config = Some(chainhook_config); - } + let mut chainhook_config = ChainhookStore::new(); + chainhook_config.register_instance(spec)?; + self.registered_chainhooks = chainhook_config; + Ok(()) } @@ -360,23 +357,6 @@ impl EventObserverConfig { bitcoin_config } - pub fn get_chainhook_store(&self) -> ChainhookStore { - let mut chainhook_store = ChainhookStore::new(); - // If authorization not required, we create a default ChainhookConfig - if let Some(ref chainhook_config) = self.chainhook_config { - let mut chainhook_config = chainhook_config.clone(); - chainhook_store - .predicates - .stacks_chainhooks - .append(&mut chainhook_config.stacks_chainhooks); - chainhook_store - .predicates - .bitcoin_chainhooks - .append(&mut chainhook_config.bitcoin_chainhooks); - } - chainhook_store - } - pub fn get_stacks_node_config(&self) -> &StacksNodeConfig { match self.bitcoin_block_signaling { BitcoinBlockSignaling::Stacks(ref config) => config, @@ -406,7 +386,7 @@ impl EventObserverConfig { let config = EventObserverConfig { bitcoin_rpc_proxy_enabled: false, - chainhook_config: None, + registered_chainhooks: ChainhookStore::new(), bitcoind_rpc_username: overrides .and_then(|c| c.bitcoind_rpc_username.clone()) .unwrap_or("devnet".to_string()), @@ -584,22 +564,6 @@ pub struct BitcoinConfig { pub bitcoin_block_signaling: BitcoinBlockSignaling, } -#[derive(Debug, Clone)] -pub struct ChainhookStore { - pub predicates: ChainhookConfig, -} - -impl ChainhookStore { - pub fn new() -> Self { - Self { - predicates: ChainhookConfig { - stacks_chainhooks: vec![], - bitcoin_chainhooks: vec![], - }, - } - } -} - #[derive(Debug, Clone)] pub struct BitcoinBlockDataCached { pub block: BitcoinBlockData, @@ -777,7 +741,7 @@ pub async fn start_bitcoin_event_observer( observer_sidecar: Option, ctx: Context, ) -> Result<(), Box> { - let chainhook_store = config.get_chainhook_store(); + let chainhook_store = config.registered_chainhooks.clone(); #[cfg(feature = "zeromq")] { let ctx_moved = ctx.clone(); @@ -790,8 +754,8 @@ pub async fn start_bitcoin_event_observer( let prometheus_monitoring = PrometheusMonitoring::new(); prometheus_monitoring.initialize( - chainhook_store.predicates.stacks_chainhooks.len() as u64, - chainhook_store.predicates.bitcoin_chainhooks.len() as u64, + chainhook_store.stacks_chainhooks.len() as u64, + chainhook_store.bitcoin_chainhooks.len() as u64, None, ); @@ -857,7 +821,7 @@ pub async fn start_stacks_event_observer( let bitcoin_rpc_proxy_enabled = config.bitcoin_rpc_proxy_enabled; let bitcoin_config = config.get_bitcoin_config(); - let chainhook_store = config.get_chainhook_store(); + let chainhook_store = config.registered_chainhooks.clone(); let indexer_rw_lock = Arc::new(RwLock::new(indexer)); @@ -865,8 +829,8 @@ pub async fn start_stacks_event_observer( let prometheus_monitoring = PrometheusMonitoring::new(); prometheus_monitoring.initialize( - chainhook_store.predicates.stacks_chainhooks.len() as u64, - chainhook_store.predicates.bitcoin_chainhooks.len() as u64, + chainhook_store.stacks_chainhooks.len() as u64, + chainhook_store.bitcoin_chainhooks.len() as u64, Some(stacks_startup_context.last_block_height_appended), ); @@ -1309,7 +1273,6 @@ pub async fn start_observer_commands_handler( let mut report = PredicateEvaluationReport::new(); let bitcoin_chainhooks = chainhook_store - .predicates .bitcoin_chainhooks .iter() .filter(|p| p.enabled) @@ -1436,7 +1399,6 @@ pub async fn start_observer_commands_handler( for hook_uuid in hooks_ids_to_deregister.iter() { if chainhook_store - .predicates .deregister_bitcoin_hook(hook_uuid.clone()) .is_some() { @@ -1455,9 +1417,7 @@ pub async fn start_observer_commands_handler( } } Err(e) => { - chainhook_store - .predicates - .deregister_bitcoin_hook(data.chainhook.uuid.clone()); + chainhook_store.deregister_bitcoin_hook(data.chainhook.uuid.clone()); if let Some(ref tx) = observer_events_tx { let _ = tx.send(ObserverEvent::PredicateInterrupted(PredicateInterruptedData { predicate_key: ChainhookInstance::bitcoin_key(&data.chainhook.uuid), @@ -1483,7 +1443,6 @@ pub async fn start_observer_commands_handler( let mut report = PredicateEvaluationReport::new(); let stacks_chainhooks = chainhook_store - .predicates .stacks_chainhooks .iter() .filter(|p| p.enabled) @@ -1617,7 +1576,6 @@ pub async fn start_observer_commands_handler( for hook_uuid in hooks_ids_to_deregister.iter() { if chainhook_store - .predicates .deregister_stacks_hook(hook_uuid.clone()) .is_some() { @@ -1644,9 +1602,7 @@ pub async fn start_observer_commands_handler( } } Err(e) => { - chainhook_store - .predicates - .deregister_stacks_hook(data.chainhook.uuid.clone()); + chainhook_store.deregister_stacks_hook(data.chainhook.uuid.clone()); if let Some(ref tx) = observer_events_tx { let _ = tx.send(ObserverEvent::PredicateInterrupted(PredicateInterruptedData { predicate_key: ChainhookInstance::stacks_key(&data.chainhook.uuid), @@ -1682,22 +1638,20 @@ pub async fn start_observer_commands_handler( ObserverCommand::RegisterPredicate(spec) => { ctx.try_log(|logger| slog::info!(logger, "Handling RegisterPredicate command")); - let mut spec = match chainhook_store - .predicates - .register_instance_from_network_map(networks, spec) - { - Ok(spec) => spec, - Err(e) => { - ctx.try_log(|logger| { - slog::warn!( - logger, - "Unable to register new chainhook spec: {}", - e.to_string() - ) - }); - continue; - } - }; + let mut spec = + match chainhook_store.register_instance_from_network_map(networks, spec) { + Ok(spec) => spec, + Err(e) => { + ctx.try_log(|logger| { + slog::warn!( + logger, + "Unable to register new chainhook spec: {}", + e.to_string() + ) + }); + continue; + } + }; match spec { ChainhookInstance::Bitcoin(_) => { @@ -1717,12 +1671,12 @@ pub async fn start_observer_commands_handler( ctx.try_log(|logger| { slog::debug!(logger, "Enabling Predicate {}", spec.uuid()) }); - chainhook_store.predicates.enable_instance(&mut spec); + chainhook_store.enable_instance(&mut spec); } } ObserverCommand::EnablePredicate(mut spec) => { ctx.try_log(|logger| slog::info!(logger, "Enabling Predicate {}", spec.uuid())); - chainhook_store.predicates.enable_instance(&mut spec); + chainhook_store.enable_instance(&mut spec); if let Some(ref tx) = observer_events_tx { let _ = tx.send(ObserverEvent::PredicateEnabled(spec)); } @@ -1731,9 +1685,7 @@ pub async fn start_observer_commands_handler( ctx.try_log(|logger| { slog::info!(logger, "Handling DeregisterStacksPredicate command") }); - let hook = chainhook_store - .predicates - .deregister_stacks_hook(hook_uuid.clone()); + let hook = chainhook_store.deregister_stacks_hook(hook_uuid.clone()); if hook.is_some() { // on startup, only the predicates in the `chainhook_store` are added to the monitoring count, @@ -1749,9 +1701,7 @@ pub async fn start_observer_commands_handler( ctx.try_log(|logger| { slog::info!(logger, "Handling DeregisterBitcoinPredicate command") }); - let hook = chainhook_store - .predicates - .deregister_bitcoin_hook(hook_uuid.clone()); + let hook = chainhook_store.deregister_bitcoin_hook(hook_uuid.clone()); if hook.is_some() { // on startup, only the predicates in the `chainhook_store` are added to the monitoring count, @@ -1768,9 +1718,7 @@ pub async fn start_observer_commands_handler( block_height, }) => { ctx.try_log(|logger| slog::info!(logger, "Handling ExpireStacksPredicate command")); - chainhook_store - .predicates - .expire_stacks_hook(hook_uuid, block_height); + chainhook_store.expire_stacks_hook(hook_uuid, block_height); } ObserverCommand::ExpireBitcoinPredicate(HookExpirationData { hook_uuid, @@ -1779,9 +1727,7 @@ pub async fn start_observer_commands_handler( ctx.try_log(|logger| { slog::info!(logger, "Handling ExpireBitcoinPredicate command") }); - chainhook_store - .predicates - .expire_bitcoin_hook(hook_uuid, block_height); + chainhook_store.expire_bitcoin_hook(hook_uuid, block_height); } } } diff --git a/components/chainhook-sdk/src/observer/tests/mod.rs b/components/chainhook-sdk/src/observer/tests/mod.rs index 41cbd56a2..0eaa0ea8d 100644 --- a/components/chainhook-sdk/src/observer/tests/mod.rs +++ b/components/chainhook-sdk/src/observer/tests/mod.rs @@ -11,7 +11,7 @@ use crate::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; use crate::chainhooks::stacks::StacksContractCallBasedPredicate; use crate::chainhooks::stacks::StacksPredicate; use crate::chainhooks::types::{ - ChainhookConfig, ChainhookInstance, ChainhookSpecificationNetworkMap, ExactMatchingRule, + ChainhookInstance, ChainhookSpecificationNetworkMap, ChainhookStore, ExactMatchingRule, HookAction, }; use crate::indexer::fork_scratch_pad::ForkScratchPad; @@ -21,8 +21,7 @@ use crate::indexer::tests::helpers::{ }; use crate::monitoring::PrometheusMonitoring; use crate::observer::{ - start_observer_commands_handler, ChainhookStore, EventObserverConfig, ObserverCommand, - ObserverSidecar, + start_observer_commands_handler, EventObserverConfig, ObserverCommand, ObserverSidecar, }; use crate::utils::{AbstractBlock, Context}; use chainhook_types::{ @@ -39,7 +38,7 @@ use super::{ObserverEvent, DEFAULT_INGESTION_PORT}; fn generate_test_config() -> (EventObserverConfig, ChainhookStore) { let config: EventObserverConfig = EventObserverConfig { - chainhook_config: Some(ChainhookConfig::new()), + registered_chainhooks: ChainhookStore::new(), bitcoin_rpc_proxy_enabled: false, bitcoind_rpc_username: "user".into(), bitcoind_rpc_password: "user".into(), @@ -52,9 +51,7 @@ fn generate_test_config() -> (EventObserverConfig, ChainhookStore) { stacks_network: StacksNetwork::Devnet, prometheus_monitoring_port: None, }; - let predicates = ChainhookConfig::new(); - let chainhook_store = ChainhookStore { predicates }; - (config, chainhook_store) + (config, ChainhookStore::new()) } fn stacks_chainhook_contract_call( From 08c15ba1c15c8e54873d68ba38f402a945bddf23 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 13:25:43 -0400 Subject: [PATCH 17/28] rs doc comment --- components/chainhook-sdk/src/observer/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index a6affe56e..fc4eb12c7 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -324,6 +324,7 @@ impl EventObserverConfig { } } + /// Adds a [ChainhookInstance] to config's the registered chainhook store. pub fn register_chainhook_instance(&mut self, spec: ChainhookInstance) -> Result<(), String> { let mut chainhook_config = ChainhookStore::new(); chainhook_config.register_instance(spec)?; @@ -332,13 +333,14 @@ impl EventObserverConfig { Ok(()) } + /// Adds a [BitcoinChainhookInstance] to the config's registered chainhook store. pub fn register_bitcoin_chainhook_instance( &mut self, spec: BitcoinChainhookInstance, ) -> Result<(), String> { self.register_chainhook_instance(ChainhookInstance::Bitcoin(spec)) } - + /// Adds a [StacksChainhookInstance] to the config's registered chainhook store. pub fn register_stacks_chainhook_instance( &mut self, spec: StacksChainhookInstance, From cee7be659051cef49144695fbe9959e699e9b546 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 13:29:40 -0400 Subject: [PATCH 18/28] fix helpers to return self --- components/chainhook-sdk/src/observer/mod.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index fc4eb12c7..dc64ed91b 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -324,27 +324,30 @@ impl EventObserverConfig { } } - /// Adds a [ChainhookInstance] to config's the registered chainhook store. - pub fn register_chainhook_instance(&mut self, spec: ChainhookInstance) -> Result<(), String> { + /// Adds a [ChainhookInstance] to config's the registered chainhook store, returning the updated config. + pub fn register_chainhook_instance( + &mut self, + spec: ChainhookInstance, + ) -> Result<&mut Self, String> { let mut chainhook_config = ChainhookStore::new(); chainhook_config.register_instance(spec)?; self.registered_chainhooks = chainhook_config; - Ok(()) + Ok(self) } - /// Adds a [BitcoinChainhookInstance] to the config's registered chainhook store. + /// Adds a [BitcoinChainhookInstance] to the config's registered chainhook store, returning the updated config. pub fn register_bitcoin_chainhook_instance( &mut self, spec: BitcoinChainhookInstance, - ) -> Result<(), String> { + ) -> Result<&mut Self, String> { self.register_chainhook_instance(ChainhookInstance::Bitcoin(spec)) } - /// Adds a [StacksChainhookInstance] to the config's registered chainhook store. + /// Adds a [StacksChainhookInstance] to the config's registered chainhook store, returning the updated config. pub fn register_stacks_chainhook_instance( &mut self, spec: StacksChainhookInstance, - ) -> Result<(), String> { + ) -> Result<&mut Self, String> { self.register_chainhook_instance(ChainhookInstance::Stacks(spec)) } From dddc69a68ac22b5762b67ec9f822e2f7e7f1f5c6 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 13:30:33 -0400 Subject: [PATCH 19/28] rename `BitcoinEventObserverBuilder` -> `BitcoinEventObserverConfigBuilder` --- components/chainhook-sdk/src/observer/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index dc64ed91b..12dc675bb 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -202,16 +202,16 @@ impl EventObserverConfigBuilder { /// ## Examples /// ``` /// use chainhook_sdk::observer::EventObserverConfig; -/// use chainhook_sdk::observer::BitcoinEventObserverBuilder; +/// use chainhook_sdk::observer::BitcoinEventObserverConfigBuilder; /// /// fn get_config() -> Result { -/// BitcoinEventObserverBuilder::new() +/// BitcoinEventObserverConfigBuilder::new() /// .rpc_password("my_password") /// .network("mainnet") /// .finish() /// } /// ``` -pub struct BitcoinEventObserverBuilder { +pub struct BitcoinEventObserverConfigBuilder { pub bitcoind_rpc_username: Option, pub bitcoind_rpc_password: Option, pub bitcoind_rpc_url: Option, @@ -219,9 +219,9 @@ pub struct BitcoinEventObserverBuilder { pub bitcoind_zmq_url: Option, pub prometheus_monitoring_port: Option, } -impl BitcoinEventObserverBuilder { +impl BitcoinEventObserverConfigBuilder { pub fn new() -> Self { - BitcoinEventObserverBuilder { + BitcoinEventObserverConfigBuilder { bitcoind_rpc_username: None, bitcoind_rpc_password: None, bitcoind_rpc_url: None, @@ -267,7 +267,7 @@ impl BitcoinEventObserverBuilder { self } - /// Attempts to convert a [BitcoinEventObserverBuilder] instance into an [EventObserverConfig], filling in + /// Attempts to convert a [BitcoinEventObserverConfigBuilder] instance into an [EventObserverConfig], filling in /// defaults as necessary according to [EventObserverConfig::default]. /// /// This function will return an error if the `bitcoin_network` string is set and is not a valid [BitcoinNetwork]. From fe04dd9aca396eb939c50748b8824849ea05fb8a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 14:01:28 -0400 Subject: [PATCH 20/28] add builder for starting an event observer --- components/chainhook-sdk/src/observer/mod.rs | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index 12dc675bb..c790b9477 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -644,6 +644,89 @@ impl ObserverSidecar { } } +/// A helper struct used to configure and call [start_event_observer], which spawns a thread to observer chain events. +/// +/// ### Examples +/// ``` +/// use chainhook_sdk::observer::EventObserver; +/// +/// fn start_event_observer() -> Result<(), Box> { +/// EventObserver::new( +/// config, +/// &observer_commands_tx, +/// observer_commands_rx, +/// &ctx +/// ) +/// .stacks_startup_context(context) +/// .start() +/// } +/// ``` +pub struct EventObserverBuilder { + config: EventObserverConfig, + observer_commands_tx: Sender, + observer_commands_rx: Receiver, + ctx: Context, + observer_events_tx: Option>, + observer_sidecar: Option, + stacks_startup_context: Option, +} + +impl EventObserverBuilder { + pub fn new( + config: EventObserverConfig, + observer_commands_tx: &Sender, + observer_commands_rx: Receiver, + ctx: &Context, + ) -> Self { + EventObserverBuilder { + config: config, + observer_commands_tx: observer_commands_tx.clone(), + observer_commands_rx: observer_commands_rx, + ctx: ctx.clone(), + observer_events_tx: None, + observer_sidecar: None, + stacks_startup_context: None, + } + } + + /// Sets the `observer_events_tx` Sender. Set this and listen on the corresponding + /// Receiver to be notified of every [ObserverEvent]. + pub fn events_tx( + &mut self, + observer_events_tx: crossbeam_channel::Sender, + ) -> &mut Self { + self.observer_events_tx = Some(observer_events_tx); + self + } + + /// Sets a sidecar for the observer. See [ObserverSidecar]. + pub fn sidecar(&mut self, sidecar: ObserverSidecar) -> &mut Self { + self.observer_sidecar = Some(sidecar); + self + } + + /// Sets the Stacks startup context. See [StacksObserverStartupContext]. + pub fn stacks_startup_context(&mut self, context: StacksObserverStartupContext) -> &mut Self { + self.stacks_startup_context = Some(context); + self + } + + /// Starts the event observer, calling [start_event_observer]. This function consumes the + /// [EventObserverBuilder] and spawns a new thread to run the observer. + pub fn start(self) -> Result<(), Box> { + start_event_observer( + self.config, + self.observer_commands_tx, + self.observer_commands_rx, + self.observer_events_tx, + self.observer_sidecar, + self.stacks_startup_context, + self.ctx, + ) + } +} + +/// Spawns a thread to observe blockchain events. Use [EventObserverBuilder] to configure easily. pub fn start_event_observer( config: EventObserverConfig, observer_commands_tx: Sender, From 1d1b1f25946480e548d603b9f50dbb017568ebee Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 15:11:09 -0400 Subject: [PATCH 21/28] rs doc for ChainhookSpecificationNetworkMap --- .../src/chainhooks/bitcoin/mod.rs | 39 +++++++++++++++++++ .../src/chainhooks/stacks/mod.rs | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs index 89e0f6e2f..33f1821b6 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs @@ -49,6 +49,45 @@ pub struct BitcoinChainhookSpecification { pub action: HookAction, } +/// Maps some [BitcoinChainhookSpecification] to a corresponding [BitcoinNetwork]. This allows maintaining one +/// serialized predicate file for a given predicate on each network. +/// +/// ### Examples +/// Given some file `predicate.json`: +/// ```json +/// { +/// "uuid": "my-id", +/// "name": "My Predicate", +/// "chain": "bitcoin", +/// "version": 1, +/// "networks": { +/// "regtest": { +/// // ... +/// }, +/// "testnet": { +/// // ... +/// }, +/// "mainnet": { +/// // ... +/// } +/// } +/// } +/// ``` +/// You can deserialize the file to this type and create a [BitcoinChainhookInstance] for the desired network: +/// ``` +/// use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookSpecificationNetworkMap; +/// use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookInstance; +/// use chainhook_types::BitcoinNetwork; +/// +/// fn get_predicate(network: &BitcoinNetwork) -> Result { +/// let json_predicate = +/// std::fs::read_to_string("./predicate.json").expect("Unable to read file"); +/// let hook_map: BitcoinChainhookSpecificationNetworkMap = +/// serde_json::from_str(&json_predicate).expect("Unable to parse Chainhook map"); +/// hook_map.into_specification_for_network(network) +/// } +/// +/// ``` #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] pub struct BitcoinChainhookSpecificationNetworkMap { pub uuid: String, diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index d6991f4cf..524743441 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -40,6 +40,45 @@ pub struct StacksChainhookSpecification { pub action: HookAction, } +/// Maps some [StacksChainhookSpecification] to a corresponding [StacksNetwork]. This allows maintaining one +/// serialized predicate file for a given predicate on each network. +/// +/// ### Examples +/// Given some file `predicate.json`: +/// ```json +/// { +/// "uuid": "my-id", +/// "name": "My Predicate", +/// "chain": "stacks", +/// "version": 1, +/// "networks": { +/// "devnet": { +/// // ... +/// }, +/// "testnet": { +/// // ... +/// }, +/// "mainnet": { +/// // ... +/// } +/// } +/// } +/// ``` +/// You can deserialize the file to this type and create a [StacksChainhookInstance] for the desired network: +/// ``` +/// use chainhook_sdk::chainhook::stacks::StacksChainhookSpecificationNetworkMap; +/// use chainhook_sdk::chainhook::stacks::StacksChainhookInstance; +/// use chainhook_types::StacksNetwork; +/// +/// fn get_predicate(network: &StacksNetwork) -> Result { +/// let json_predicate = +/// std::fs::read_to_string("./predicate.json").expect("Unable to read file"); +/// let hook_map: StacksChainhookSpecificationNetworkMap = +/// serde_json::from_str(&json_predicate).expect("Unable to parse Chainhook map"); +/// hook_map.into_specification_for_network(network) +/// } +/// +/// ``` #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] pub struct StacksChainhookSpecificationNetworkMap { pub uuid: String, From 8731e3c6b6527b6e5762cb8d56c475bb2a181e10 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 15:11:18 -0400 Subject: [PATCH 22/28] fix rsdoc --- components/chainhook-sdk/src/indexer/bitcoin/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/chainhook-sdk/src/indexer/bitcoin/mod.rs b/components/chainhook-sdk/src/indexer/bitcoin/mod.rs index 2af20ea63..c28826020 100644 --- a/components/chainhook-sdk/src/indexer/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/indexer/bitcoin/mod.rs @@ -91,9 +91,8 @@ pub struct GetRawTransactionResultVinScriptSig { } impl BitcoinTransactionInputFullBreakdown { - /// Whether this input is from a coinbase tx. - /// The [txid], [vout] and [script_sig] fields are not provided - /// for coinbase transactions. + /// Whether this input is from a coinbase tx. If there is not a [BitcoinTransactionInputFullBreakdown::txid] field, the transaction is a coinbase transaction. + // Note: vout and script_sig fields are also not provided for coinbase transactions. pub fn is_coinbase(&self) -> bool { self.txid.is_none() } From 7e99a3ce2c98acf5bc88085b200357a38bc9b2c4 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 15:11:33 -0400 Subject: [PATCH 23/28] clean up --- components/chainhook-sdk/src/observer/mod.rs | 3 +-- components/chainhook-types-rs/src/bitcoin.rs | 2 +- components/chainhook-types-rs/src/rosetta.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index c790b9477..f6ad4be00 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -37,7 +37,6 @@ use rocket::config::{self, Config, LogLevel}; use rocket::data::{Limits, ToByteUnit}; use rocket::serde::Deserialize; use rocket::Shutdown; -use std::borrow::BorrowMut; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::error::Error; use std::net::{IpAddr, Ipv4Addr}; @@ -187,7 +186,7 @@ impl EventObserverConfigBuilder { self } - /// Attempts to convert a [EventObserverConfigOverrides] instance into an [EventObserverConfig], filling in + /// Attempts to convert a [EventObserverConfigBuilder] instance into an [EventObserverConfig], filling in /// defaults as necessary according to [EventObserverConfig::default]. /// /// This function will return an error if the `bitcoin_network` or `stacks_network` strings are set and are not a valid [BitcoinNetwork] or [StacksNetwork]. diff --git a/components/chainhook-types-rs/src/bitcoin.rs b/components/chainhook-types-rs/src/bitcoin.rs index 79743bb14..7718b65b7 100644 --- a/components/chainhook-types-rs/src/bitcoin.rs +++ b/components/chainhook-types-rs/src/bitcoin.rs @@ -63,7 +63,7 @@ impl TxOut { /// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)] pub struct Witness { - /// contains the witness Vec> serialization without the initial varint indicating the + /// contains the witness `Vec>` serialization without the initial varint indicating the /// number of elements (which is stored in `witness_elements`) content: Vec, diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs index d917912cd..a794a27b0 100644 --- a/components/chainhook-types-rs/src/rosetta.rs +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -456,9 +456,9 @@ pub struct PublicKey { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum CurveType { - /// `y (255-bits) || x-sign-bit (1-bit)` - `32 bytes` (https://ed25519.cr.yp.to/ed25519-20110926.pdf) + /// `y (255-bits) || x-sign-bit (1-bit)` - `32 bytes` () Edwards25519, - /// SEC compressed - `33 bytes` (https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) + /// SEC compressed - `33 bytes` () Secp256k1, } From ea66e72c70b823c59b1cc91f1d11ce06f56e6cf4 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 15:17:51 -0400 Subject: [PATCH 24/28] update openapi spec --- docs/chainhook-openapi.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/chainhook-openapi.json b/docs/chainhook-openapi.json index 93e9279bf..770d9752e 100644 --- a/docs/chainhook-openapi.json +++ b/docs/chainhook-openapi.json @@ -157,6 +157,7 @@ "ChainhookSpecificationNetworkMap": { "oneOf": [ { + "description": "Maps some [BitcoinChainhookSpecification] to a corresponding [BitcoinNetwork]. This allows maintaining one serialized predicate file for a given predicate on each network.\n\n### Examples Given some file `predicate.json`: ```json { \"uuid\": \"my-id\", \"name\": \"My Predicate\", \"chain\": \"bitcoin\", \"version\": 1, \"networks\": { \"regtest\": { // ... }, \"testnet\": { // ... }, \"mainnet\": { // ... } } } ``` You can deserialize the file to this type and create a [BitcoinChainhookInstance] for the desired network: ``` use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookSpecificationNetworkMap; use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookInstance; use chainhook_types::BitcoinNetwork;\n\nfn get_predicate(network: &BitcoinNetwork) -> Result { let json_predicate = std::fs::read_to_string(\"./predicate.json\").expect(\"Unable to read file\"); let hook_map: BitcoinChainhookSpecificationNetworkMap = serde_json::from_str(&json_predicate).expect(\"Unable to parse Chainhook map\"); hook_map.into_specification_for_network(network) }\n\n```", "type": "object", "required": [ "chain", @@ -238,6 +239,7 @@ } }, { + "description": "Maps some [StacksChainhookSpecification] to a corresponding [StacksNetwork]. This allows maintaining one serialized predicate file for a given predicate on each network.\n\n### Examples Given some file `predicate.json`: ```json { \"uuid\": \"my-id\", \"name\": \"My Predicate\", \"chain\": \"stacks\", \"version\": 1, \"networks\": { \"devnet\": { // ... }, \"testnet\": { // ... }, \"mainnet\": { // ... } } } ``` You can deserialize the file to this type and create a [StacksChainhookInstance] for the desired network: ``` use chainhook_sdk::chainhook::stacks::StacksChainhookSpecificationNetworkMap; use chainhook_sdk::chainhook::stacks::StacksChainhookInstance; use chainhook_types::StacksNetwork;\n\nfn get_predicate(network: &StacksNetwork) -> Result { let json_predicate = std::fs::read_to_string(\"./predicate.json\").expect(\"Unable to read file\"); let hook_map: StacksChainhookSpecificationNetworkMap = serde_json::from_str(&json_predicate).expect(\"Unable to parse Chainhook map\"); hook_map.into_specification_for_network(network) }\n\n```", "type": "object", "required": [ "chain", From 2812b21cb757fc8d72ffef1c937ad577f29bf18e Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 24 Jun 2024 15:39:53 -0400 Subject: [PATCH 25/28] `uwrap_or_else` instead of `unwrap_or` --- components/chainhook-sdk/src/observer/mod.rs | 34 +++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index f6ad4be00..d39aef22b 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -282,19 +282,19 @@ impl BitcoinEventObserverConfigBuilder { bitcoind_rpc_username: self .bitcoind_rpc_username .clone() - .unwrap_or("devnet".into()), + .unwrap_or_else(|| "devnet".into()), bitcoind_rpc_password: self .bitcoind_rpc_password .clone() - .unwrap_or("devnet".into()), + .unwrap_or_else(|| "devnet".into()), bitcoind_rpc_url: self .bitcoind_rpc_url .clone() - .unwrap_or("http://localhost:18443".into()), + .unwrap_or_else(|| "http://localhost:18443".into()), bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ( self.bitcoind_zmq_url .clone() - .unwrap_or("tcp://0.0.0.0:18543".into()), + .unwrap_or_else(|| "tcp://0.0.0.0:18543".into()), ), display_stacks_ingestion_logs: false, bitcoin_network: bitcoin_network, @@ -393,27 +393,29 @@ impl EventObserverConfig { registered_chainhooks: ChainhookStore::new(), bitcoind_rpc_username: overrides .and_then(|c| c.bitcoind_rpc_username.clone()) - .unwrap_or("devnet".to_string()), + .unwrap_or_else(|| "devnet".to_string()), bitcoind_rpc_password: overrides .and_then(|c| c.bitcoind_rpc_password.clone()) - .unwrap_or("devnet".to_string()), + .unwrap_or_else(|| "devnet".to_string()), bitcoind_rpc_url: overrides .and_then(|c| c.bitcoind_rpc_url.clone()) - .unwrap_or("http://localhost:18443".to_string()), + .unwrap_or_else(|| "http://localhost:18443".to_string()), bitcoin_block_signaling: overrides .and_then(|c| c.bitcoind_zmq_url.as_ref()) .map(|url| BitcoinBlockSignaling::ZeroMQ(url.clone())) - .unwrap_or(BitcoinBlockSignaling::Stacks(StacksNodeConfig::new( - overrides - .and_then(|c| c.stacks_node_rpc_url.clone()) - .unwrap_or(DEFAULT_STACKS_NODE_RPC.to_string()), - overrides - .and_then(|c| c.chainhook_stacks_block_ingestion_port) - .unwrap_or(DEFAULT_INGESTION_PORT), - ))), + .unwrap_or_else(|| { + BitcoinBlockSignaling::Stacks(StacksNodeConfig::new( + overrides + .and_then(|c| c.stacks_node_rpc_url.clone()) + .unwrap_or_else(|| DEFAULT_STACKS_NODE_RPC.to_string()), + overrides + .and_then(|c| c.chainhook_stacks_block_ingestion_port) + .unwrap_or_else(|| DEFAULT_INGESTION_PORT), + )) + }), display_stacks_ingestion_logs: overrides .and_then(|c| c.display_stacks_ingestion_logs) - .unwrap_or(false), + .unwrap_or_else(|| false), bitcoin_network, stacks_network, prometheus_monitoring_port: overrides.and_then(|c| c.prometheus_monitoring_port), From 1d5106f22b1ce2dfeb99ea20aca2880062265a9d Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 26 Jun 2024 20:56:21 -0400 Subject: [PATCH 26/28] remove dead code --- components/chainhook-sdk/src/chainhooks/stacks/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index 524743441..939804a70 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -154,9 +154,7 @@ impl StacksChainhookInstance { pub fn is_predicate_targeting_block_header(&self) -> bool { match &self.predicate { - StacksPredicate::BlockHeight(_) - // | &StacksPredicate::BitcoinBlockHeight(_) - => true, + StacksPredicate::BlockHeight(_) => true, _ => false, } } @@ -185,7 +183,6 @@ pub struct StacksContractCallBasedPredicate { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -// #[serde(tag = "type", content = "rule")] pub enum StacksContractDeploymentPredicate { Deployer(String), ImplementTrait(StacksTrait), From f0eb8367e5965ef0fab438eac5947d8e81612ce6 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 26 Jun 2024 21:22:53 -0400 Subject: [PATCH 27/28] Merge branch 'main' into 'feat/improve-sdk-interface' --- .github/workflows/pkg-version-bump.yaml | 3 +- components/chainhook-cli/src/cli/mod.rs | 6 +- components/chainhook-cli/src/scan/bitcoin.rs | 23 +- components/chainhook-cli/src/scan/common.rs | 6 + components/chainhook-cli/src/scan/stacks.rs | 39 ++- components/chainhook-cli/src/service/mod.rs | 44 ++- .../chainhook-cli/src/service/runloops.rs | 274 ++++++++++-------- .../src/service/tests/helpers/mock_service.rs | 2 +- .../chainhook-cli/src/service/tests/mod.rs | 1 + .../src/service/tests/runloop_tests.rs | 170 +++++++++++ .../src/chainhooks/bitcoin/mod.rs | 4 +- .../src/chainhooks/stacks/mod.rs | 6 +- .../chainhook-sdk/src/chainhooks/types.rs | 2 +- components/chainhook-sdk/src/observer/mod.rs | 57 +++- .../chainhook-sdk/src/observer/tests/mod.rs | 25 +- components/chainhook-types-rs/src/lib.rs | 1 + docs/chainhook-openapi.json | 4 +- 17 files changed, 506 insertions(+), 161 deletions(-) create mode 100644 components/chainhook-cli/src/service/tests/runloop_tests.rs diff --git a/.github/workflows/pkg-version-bump.yaml b/.github/workflows/pkg-version-bump.yaml index 2272fd45f..63d38a6bd 100644 --- a/.github/workflows/pkg-version-bump.yaml +++ b/.github/workflows/pkg-version-bump.yaml @@ -28,7 +28,7 @@ jobs: TAG: ${{ github.event.client_payload.tag || github.event.inputs.tag }} run: | # Get version info - VERSION=$(echo "${TAG#v}") + $VERSION=${env:TAG}.substring(1) # Configure git configs git config --global user.name "${env:GIT_USER_NAME}" @@ -44,3 +44,4 @@ jobs: --token ${{ secrets.GH_TOKEN }} ` --submit ` HiroSystems.Chainhook + diff --git a/components/chainhook-cli/src/cli/mod.rs b/components/chainhook-cli/src/cli/mod.rs index f41574a6e..6859d8fa7 100644 --- a/components/chainhook-cli/src/cli/mod.rs +++ b/components/chainhook-cli/src/cli/mod.rs @@ -524,13 +524,14 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { &predicate_spec, None, &config, + None, &ctx, ) .await?; } ChainhookSpecificationNetworkMap::Stacks(predicate) => { let predicate_spec = match predicate - .into_specification_from_network(&config.network.stacks_network) + .into_specification_for_network(&config.network.stacks_network) { Ok(predicate) => predicate, Err(e) => { @@ -552,6 +553,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { None, &db_conn, &config, + None, &ctx, ) .await?; @@ -593,7 +595,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { } ChainhookSpecificationNetworkMap::Stacks(predicate) => { let _ = match predicate - .into_specification_from_network(&config.network.stacks_network) + .into_specification_for_network(&config.network.stacks_network) { Ok(predicate) => predicate, Err(e) => { diff --git a/components/chainhook-cli/src/scan/bitcoin.rs b/components/chainhook-cli/src/scan/bitcoin.rs index 31463bcf5..3437af311 100644 --- a/components/chainhook-cli/src/scan/bitcoin.rs +++ b/components/chainhook-cli/src/scan/bitcoin.rs @@ -22,13 +22,17 @@ use chainhook_sdk::types::{ }; use chainhook_sdk::utils::{file_append, send_request, Context}; use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +use super::common::PredicateScanResult; pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( predicate_spec: &BitcoinChainhookInstance, unfinished_scan_data: Option, config: &Config, + kill_signal: Option>>, ctx: &Context, -) -> Result { +) -> Result { let predicate_uuid = &predicate_spec.uuid; let auth = Auth::UserPass( config.network.bitcoind_rpc_username.clone(), @@ -62,7 +66,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( let mut block_heights_to_scan = match block_heights_to_scan { Some(h) => h, // no blocks to scan, go straight to streaming - None => return Ok(false), + None => return Ok(PredicateScanResult::ChainTipReached), }; let mut predicates_db_conn = match config.http_api { @@ -100,6 +104,17 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( let mut loop_did_trigger = false; while let Some(current_block_height) = block_heights_to_scan.pop_front() { + if let Some(kill_signal) = kill_signal.clone() { + match kill_signal.read() { + Ok(kill_signal) => { + // if true, we're received the kill signal, so break out of the loop + if *kill_signal { + return Ok(PredicateScanResult::Deregistered); + } + } + Err(_) => {} + } + } if let Some(ref mut predicates_db_conn) = predicates_db_conn { if number_of_blocks_scanned % 100 == 0 || number_of_blocks_scanned == 0 @@ -242,10 +257,10 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate( set_confirmed_expiration_status(&predicate_spec.key(), predicates_db_conn, ctx); } } - return Ok(true); + return Ok(PredicateScanResult::Expired); } - return Ok(false); + return Ok(PredicateScanResult::ChainTipReached); } pub async fn process_block_with_predicates( diff --git a/components/chainhook-cli/src/scan/common.rs b/components/chainhook-cli/src/scan/common.rs index c6261dff8..ee5ddef61 100644 --- a/components/chainhook-cli/src/scan/common.rs +++ b/components/chainhook-cli/src/scan/common.rs @@ -65,3 +65,9 @@ pub fn get_block_heights_to_scan( }; Ok(block_heights_to_scan) } + +pub enum PredicateScanResult { + ChainTipReached, + Expired, + Deregistered, +} diff --git a/components/chainhook-cli/src/scan/stacks.rs b/components/chainhook-cli/src/scan/stacks.rs index 2b88b1efc..55e44df9a 100644 --- a/components/chainhook-cli/src/scan/stacks.rs +++ b/components/chainhook-cli/src/scan/stacks.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, VecDeque}; +use std::{ + collections::{HashMap, VecDeque}, + sync::{Arc, RwLock}, +}; use crate::{ archive::download_stacks_dataset_if_required, @@ -29,6 +32,8 @@ use chainhook_sdk::{ }; use rocksdb::DB; +use super::common::PredicateScanResult; + #[derive(Debug, Clone, Eq, PartialEq)] pub enum DigestingCommand { DigestSeedBlock(BlockIdentifier), @@ -170,16 +175,17 @@ pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( unfinished_scan_data: Option, stacks_db_conn: &DB, config: &Config, + kill_signal: Option>>, ctx: &Context, -) -> Result<(Option, bool), String> { +) -> Result { let predicate_uuid = &predicate_spec.uuid; let mut chain_tip = match get_last_unconfirmed_block_height_inserted(stacks_db_conn, ctx) { Some(chain_tip) => chain_tip, None => match get_last_block_height_inserted(stacks_db_conn, ctx) { Some(chain_tip) => chain_tip, None => { - info!(ctx.expect_logger(), "No blocks inserted in db; cannot determing Stacks chain tip. Skipping scan of predicate {}", predicate_uuid); - return Ok((None, false)); + info!(ctx.expect_logger(), "No blocks inserted in db; cannot determine Stacks chain tip. Skipping scan of predicate {}", predicate_uuid); + return Ok(PredicateScanResult::ChainTipReached); } }, }; @@ -194,7 +200,13 @@ pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( let mut block_heights_to_scan = match block_heights_to_scan { Some(h) => h, // no blocks to scan, go straight to streaming - None => return Ok((None, false)), + None => { + debug!( + ctx.expect_logger(), + "Stacks chainstate scan completed. 0 blocks scanned." + ); + return Ok(PredicateScanResult::ChainTipReached); + } }; let mut predicates_db_conn = match config.http_api { @@ -226,6 +238,17 @@ pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( let mut loop_did_trigger = false; while let Some(current_block_height) = block_heights_to_scan.pop_front() { + if let Some(kill_signal) = kill_signal.clone() { + match kill_signal.read() { + Ok(kill_signal) => { + // if true, we're received the kill signal, so break out of the loop + if *kill_signal { + return Ok(PredicateScanResult::Deregistered); + } + } + Err(_) => {} + } + } if let Some(ref mut predicates_db_conn) = predicates_db_conn { if number_of_blocks_scanned % 1000 == 0 || number_of_blocks_scanned == 0 @@ -255,7 +278,7 @@ pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( Some(chain_tip) => chain_tip, None => { warn!(ctx.expect_logger(), "No blocks inserted in db; cannot determine Stacks chain tip. Skipping scan of predicate {}", predicate_uuid); - return Ok((None, false)); + return Ok(PredicateScanResult::ChainTipReached); } }, }; @@ -411,10 +434,10 @@ pub async fn scan_stacks_chainstate_via_rocksdb_using_predicate( set_confirmed_expiration_status(&predicate_spec.key(), predicates_db_conn, ctx); } } - return Ok((Some(last_block_scanned), true)); + return Ok(PredicateScanResult::Expired); } - Ok((Some(last_block_scanned), false)) + Ok(PredicateScanResult::ChainTipReached) } pub async fn scan_stacks_chainstate_via_csv_using_predicate( diff --git a/components/chainhook-cli/src/service/mod.rs b/components/chainhook-cli/src/service/mod.rs index 165816201..7019b4e2e 100644 --- a/components/chainhook-cli/src/service/mod.rs +++ b/components/chainhook-cli/src/service/mod.rs @@ -16,7 +16,8 @@ use chainhook_sdk::chainhooks::types::{ChainhookSpecificationNetworkMap, Chainho use chainhook_sdk::chainhooks::types::ChainhookInstance; use chainhook_sdk::observer::{ start_event_observer, HookExpirationData, ObserverCommand, ObserverEvent, - PredicateEvaluationReport, PredicateInterruptedData, StacksObserverStartupContext, + PredicateDeregisteredEvent, PredicateEvaluationReport, PredicateInterruptedData, + StacksObserverStartupContext, }; use chainhook_sdk::types::{Chain, StacksBlockData, StacksChainEvent}; use chainhook_sdk::utils::Context; @@ -26,6 +27,7 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::{SystemTime, UNIX_EPOCH}; use self::http_api::get_entry_from_predicates_db; +use self::runloops::{BitcoinScanOp, StacksScanOp}; pub struct Service { config: Config, @@ -304,10 +306,16 @@ impl Service { for predicate_with_last_scanned_block in leftover_scans { match predicate_with_last_scanned_block { (ChainhookInstance::Stacks(spec), last_scanned_block) => { - let _ = stacks_scan_op_tx.send((spec, last_scanned_block)); + let _ = stacks_scan_op_tx.send(StacksScanOp::StartScan { + predicate_spec: spec, + unfinished_scan_data: last_scanned_block, + }); } (ChainhookInstance::Bitcoin(spec), last_scanned_block) => { - let _ = bitcoin_scan_op_tx.send((spec, last_scanned_block)); + let _ = bitcoin_scan_op_tx.send(BitcoinScanOp::StartScan { + predicate_spec: spec, + unfinished_scan_data: last_scanned_block, + }); } } } @@ -354,10 +362,16 @@ impl Service { } match spec { ChainhookInstance::Stacks(predicate_spec) => { - let _ = stacks_scan_op_tx.send((predicate_spec, None)); + let _ = stacks_scan_op_tx.send(StacksScanOp::StartScan { + predicate_spec, + unfinished_scan_data: None, + }); } ChainhookInstance::Bitcoin(predicate_spec) => { - let _ = bitcoin_scan_op_tx.send((predicate_spec, None)); + let _ = bitcoin_scan_op_tx.send(BitcoinScanOp::StartScan { + predicate_spec, + unfinished_scan_data: None, + }); } } } @@ -382,14 +396,30 @@ impl Service { ); } } - ObserverEvent::PredicateDeregistered(uuid) => { + ObserverEvent::PredicateDeregistered(PredicateDeregisteredEvent { + predicate_uuid, + chain, + }) => { if let PredicatesApi::On(ref config) = self.config.http_api { let Ok(mut predicates_db_conn) = open_readwrite_predicates_db_conn_verbose(&config, &ctx) else { continue; }; - let predicate_key = ChainhookInstance::either_stx_or_btc_key(&uuid); + + match chain { + Chain::Bitcoin => { + let _ = bitcoin_scan_op_tx + .send(BitcoinScanOp::KillScan(predicate_uuid.clone())); + } + Chain::Stacks => { + let _ = stacks_scan_op_tx + .send(StacksScanOp::KillScan(predicate_uuid.clone())); + } + }; + + let predicate_key = + ChainhookInstance::either_stx_or_btc_key(&predicate_uuid); let res: Result<(), redis::RedisError> = predicates_db_conn.del(predicate_key.clone()); if let Err(e) = res { diff --git a/components/chainhook-cli/src/service/runloops.rs b/components/chainhook-cli/src/service/runloops.rs index 2d3d89557..2322c1545 100644 --- a/components/chainhook-cli/src/service/runloops.rs +++ b/components/chainhook-cli/src/service/runloops.rs @@ -1,4 +1,7 @@ -use std::sync::mpsc::Sender; +use std::{ + collections::HashMap, + sync::{mpsc::Sender, Arc, RwLock}, +}; use chainhook_sdk::{ chainhooks::{ @@ -13,7 +16,7 @@ use threadpool::ThreadPool; use crate::{ config::{Config, PredicatesApi}, scan::{ - bitcoin::scan_bitcoin_chainstate_via_rpc_using_predicate, + bitcoin::scan_bitcoin_chainstate_via_rpc_using_predicate, common::PredicateScanResult, stacks::scan_stacks_chainstate_via_rocksdb_using_predicate, }, service::{open_readwrite_predicates_db_conn_or_panic, set_predicate_interrupted_status}, @@ -22,146 +25,191 @@ use crate::{ use super::ScanningData; +pub enum StacksScanOp { + StartScan { + predicate_spec: StacksChainhookInstance, + unfinished_scan_data: Option, + }, + KillScan(String), +} + pub fn start_stacks_scan_runloop( config: &Config, - stacks_scan_op_rx: crossbeam_channel::Receiver<(StacksChainhookInstance, Option)>, + stacks_scan_op_rx: crossbeam_channel::Receiver, observer_command_tx: Sender, ctx: &Context, ) { let stacks_scan_pool = ThreadPool::new(config.limits.max_number_of_concurrent_stacks_scans); - while let Ok((predicate_spec, unfinished_scan_data)) = stacks_scan_op_rx.recv() { - let moved_ctx = ctx.clone(); - let moved_config = config.clone(); - let observer_command_tx = observer_command_tx.clone(); - stacks_scan_pool.execute(move || { - let stacks_db_conn = - match open_readonly_stacks_db_conn(&moved_config.expected_cache_path(), &moved_ctx) - { - Ok(db_conn) => db_conn, - Err(e) => { - // todo: if we repeatedly can't connect to the database, we should restart the - // service to get to a healthy state. I don't know if this has been an issue, though - // so we can monitor and possibly remove this todo - error!( - moved_ctx.expect_logger(), - "unable to open stacks db: {}", - e.to_string() - ); - unimplemented!() - } - }; + let mut kill_signals = HashMap::new(); - let op = scan_stacks_chainstate_via_rocksdb_using_predicate( - &predicate_spec, + while let Ok(op) = stacks_scan_op_rx.recv() { + match op { + StacksScanOp::StartScan { + predicate_spec, unfinished_scan_data, - &stacks_db_conn, - &moved_config, - &moved_ctx, - ); - let res = hiro_system_kit::nestable_block_on(op); - let (last_block_scanned, predicate_is_expired) = match res { - Ok(last_block_scanned) => last_block_scanned, - Err(e) => { - warn!( - moved_ctx.expect_logger(), - "Unable to evaluate predicate on Stacks chainstate: {e}", + } => { + let moved_ctx = ctx.clone(); + let moved_config = config.clone(); + let observer_command_tx = observer_command_tx.clone(); + let kill_signal = Arc::new(RwLock::new(false)); + kill_signals.insert(predicate_spec.uuid.clone(), kill_signal.clone()); + stacks_scan_pool.execute(move || { + let stacks_db_conn = match open_readonly_stacks_db_conn( + &moved_config.expected_cache_path(), + &moved_ctx, + ) { + Ok(db_conn) => db_conn, + Err(e) => { + // todo: if we repeatedly can't connect to the database, we should restart the + // service to get to a healthy state. I don't know if this has been an issue, though + // so we can monitor and possibly remove this todo + error!( + moved_ctx.expect_logger(), + "unable to open stacks db: {}", + e.to_string() + ); + unimplemented!() + } + }; + + let op = scan_stacks_chainstate_via_rocksdb_using_predicate( + &predicate_spec, + unfinished_scan_data, + &stacks_db_conn, + &moved_config, + Some(kill_signal), + &moved_ctx, ); + let res = hiro_system_kit::nestable_block_on(op); + match res { + Ok(PredicateScanResult::Expired) + | Ok(PredicateScanResult::Deregistered) => {} + Ok(PredicateScanResult::ChainTipReached) => { + let _ = observer_command_tx.send(ObserverCommand::EnablePredicate( + ChainhookInstance::Stacks(predicate_spec), + )); + } + Err(e) => { + warn!( + moved_ctx.expect_logger(), + "Unable to evaluate predicate on Stacks chainstate: {e}", + ); - // Update predicate status in redis - if let PredicatesApi::On(ref api_config) = moved_config.http_api { - let error = - format!("Unable to evaluate predicate on Stacks chainstate: {e}"); - let mut predicates_db_conn = - open_readwrite_predicates_db_conn_or_panic(api_config, &moved_ctx); - set_predicate_interrupted_status( - error, - &predicate_spec.key(), - &mut predicates_db_conn, - &moved_ctx, - ); - } + // Update predicate status in redis + if let PredicatesApi::On(ref api_config) = moved_config.http_api { + let error = format!( + "Unable to evaluate predicate on Stacks chainstate: {e}" + ); + let mut predicates_db_conn = + open_readwrite_predicates_db_conn_or_panic( + api_config, &moved_ctx, + ); + set_predicate_interrupted_status( + error, + &predicate_spec.key(), + &mut predicates_db_conn, + &moved_ctx, + ); + } - return; - } - }; - match last_block_scanned { - Some(last_block_scanned) => { - info!( - moved_ctx.expect_logger(), - "Stacks chainstate scan completed up to block: {}", - last_block_scanned.index - ); - } - None => { - info!( - moved_ctx.expect_logger(), - "Stacks chainstate scan completed. 0 blocks scanned." - ); - } + return; + } + }; + }); } - if !predicate_is_expired { - let _ = observer_command_tx.send(ObserverCommand::EnablePredicate( - ChainhookInstance::Stacks(predicate_spec), - )); + StacksScanOp::KillScan(predicate_uuid) => { + let Some(kill_signal) = kill_signals.remove(&predicate_uuid) else { + continue; + }; + let mut kill_signal_writer = kill_signal.write().unwrap(); + *kill_signal_writer = true; } - }); + } } let _ = stacks_scan_pool.join(); } +pub enum BitcoinScanOp { + StartScan { + predicate_spec: BitcoinChainhookInstance, + unfinished_scan_data: Option, + }, + KillScan(String), +} + pub fn start_bitcoin_scan_runloop( config: &Config, - bitcoin_scan_op_rx: crossbeam_channel::Receiver<( - BitcoinChainhookInstance, - Option, - )>, + bitcoin_scan_op_rx: crossbeam_channel::Receiver, observer_command_tx: Sender, ctx: &Context, ) { let bitcoin_scan_pool = ThreadPool::new(config.limits.max_number_of_concurrent_bitcoin_scans); + let mut kill_signals = HashMap::new(); - while let Ok((predicate_spec, unfinished_scan_data)) = bitcoin_scan_op_rx.recv() { - let moved_ctx = ctx.clone(); - let moved_config = config.clone(); - let observer_command_tx = observer_command_tx.clone(); - bitcoin_scan_pool.execute(move || { - let op = scan_bitcoin_chainstate_via_rpc_using_predicate( - &predicate_spec, + while let Ok(op) = bitcoin_scan_op_rx.recv() { + match op { + BitcoinScanOp::StartScan { + predicate_spec, unfinished_scan_data, - &moved_config, - &moved_ctx, - ); + } => { + let moved_ctx = ctx.clone(); + let moved_config = config.clone(); + let observer_command_tx = observer_command_tx.clone(); + let kill_signal = Arc::new(RwLock::new(false)); + kill_signals.insert(predicate_spec.uuid.clone(), kill_signal.clone()); - let predicate_is_expired = match hiro_system_kit::nestable_block_on(op) { - Ok(predicate_is_expired) => predicate_is_expired, - Err(e) => { - warn!( - moved_ctx.expect_logger(), - "Unable to evaluate predicate on Bitcoin chainstate: {e}", + bitcoin_scan_pool.execute(move || { + let op = scan_bitcoin_chainstate_via_rpc_using_predicate( + &predicate_spec, + unfinished_scan_data, + &moved_config, + Some(kill_signal), + &moved_ctx, ); - // Update predicate status in redis - if let PredicatesApi::On(ref api_config) = moved_config.http_api { - let error = - format!("Unable to evaluate predicate on Bitcoin chainstate: {e}"); - let mut predicates_db_conn = - open_readwrite_predicates_db_conn_or_panic(api_config, &moved_ctx); - set_predicate_interrupted_status( - error, - &predicate_spec.key(), - &mut predicates_db_conn, - &moved_ctx, - ) - } - return; - } - }; - if !predicate_is_expired { - let _ = observer_command_tx.send(ObserverCommand::EnablePredicate( - ChainhookInstance::Bitcoin(predicate_spec), - )); + match hiro_system_kit::nestable_block_on(op) { + Ok(PredicateScanResult::Expired) + | Ok(PredicateScanResult::Deregistered) => {} + Ok(PredicateScanResult::ChainTipReached) => { + let _ = observer_command_tx.send(ObserverCommand::EnablePredicate( + ChainhookInstance::Bitcoin(predicate_spec), + )); + } + Err(e) => { + warn!( + moved_ctx.expect_logger(), + "Unable to evaluate predicate on Bitcoin chainstate: {e}", + ); + + // Update predicate status in redis + if let PredicatesApi::On(ref api_config) = moved_config.http_api { + let error = format!( + "Unable to evaluate predicate on Bitcoin chainstate: {e}" + ); + let mut predicates_db_conn = + open_readwrite_predicates_db_conn_or_panic( + api_config, &moved_ctx, + ); + set_predicate_interrupted_status( + error, + &predicate_spec.key(), + &mut predicates_db_conn, + &moved_ctx, + ) + } + return; + } + }; + }); + } + BitcoinScanOp::KillScan(predicate_uuid) => { + let Some(kill_signal) = kill_signals.remove(&predicate_uuid) else { + continue; + }; + let mut kill_signal_writer = kill_signal.write().unwrap(); + *kill_signal_writer = true; } - }); + } } let _ = bitcoin_scan_pool.join(); } diff --git a/components/chainhook-cli/src/service/tests/helpers/mock_service.rs b/components/chainhook-cli/src/service/tests/helpers/mock_service.rs index e26e4ba1c..7e50e59b8 100644 --- a/components/chainhook-cli/src/service/tests/helpers/mock_service.rs +++ b/components/chainhook-cli/src/service/tests/helpers/mock_service.rs @@ -425,7 +425,7 @@ pub async fn setup_stacks_chainhook_test( panic!("test failed with error: {e}"); }); let stacks_spec = predicate - .into_specification_from_network(&StacksNetwork::Devnet) + .into_specification_for_network(&StacksNetwork::Devnet) .unwrap_or_else(|e| { flush_redis(redis_port); redis_process.kill().unwrap(); diff --git a/components/chainhook-cli/src/service/tests/mod.rs b/components/chainhook-cli/src/service/tests/mod.rs index 835eabdcd..3b816b544 100644 --- a/components/chainhook-cli/src/service/tests/mod.rs +++ b/components/chainhook-cli/src/service/tests/mod.rs @@ -34,6 +34,7 @@ use super::http_api::document_predicate_api_server; pub mod helpers; mod observer_tests; +mod runloop_tests; async fn test_register_predicate(predicate: JsonValue) -> Result<(), (String, Shutdown)> { // perhaps a little janky, we bind to the port 0 to find an open one, then diff --git a/components/chainhook-cli/src/service/tests/runloop_tests.rs b/components/chainhook-cli/src/service/tests/runloop_tests.rs new file mode 100644 index 000000000..b672499d7 --- /dev/null +++ b/components/chainhook-cli/src/service/tests/runloop_tests.rs @@ -0,0 +1,170 @@ +use std::{path::PathBuf, sync::mpsc::channel, thread::sleep, time::Duration}; + +use chainhook_sdk::{ + chainhooks::{ + bitcoin::{BitcoinChainhookInstance, BitcoinPredicateType}, + stacks::{StacksChainhookInstance, StacksPredicate}, + types::{BlockIdentifierIndexRule, HookAction}, + }, + types::{BitcoinNetwork, StacksNetwork}, + utils::Context, +}; + +use crate::{ + config::{Config, EventSourceConfig, PathConfig}, + scan::stacks::consolidate_local_stacks_chainstate_using_csv, + service::{ + runloops::{ + start_bitcoin_scan_runloop, start_stacks_scan_runloop, BitcoinScanOp, StacksScanOp, + }, + tests::helpers::{ + mock_bitcoin_rpc::mock_bitcoin_rpc, mock_service::setup_chainhook_service_ports, + }, + }, +}; + +use super::helpers::mock_stacks_node::{create_tmp_working_dir, write_stacks_blocks_to_tsv}; + +#[tokio::test] +async fn test_stacks_runloop_kill_scan() { + let (working_dir, tsv_dir) = create_tmp_working_dir().unwrap_or_else(|e| { + panic!("test failed with error: {e}"); + }); + + write_stacks_blocks_to_tsv(1000, &tsv_dir).unwrap_or_else(|e| { + std::fs::remove_dir_all(&working_dir).unwrap(); + panic!("test failed with error: {e}"); + }); + + let mut config = Config::devnet_default(); + config.storage.working_dir = working_dir.clone(); + config.event_sources = vec![EventSourceConfig::StacksTsvPath(PathConfig { + file_path: PathBuf::from(tsv_dir), + })]; + + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + + consolidate_local_stacks_chainstate_using_csv(&mut config, &ctx) + .await + .unwrap_or_else(|e| { + std::fs::remove_dir_all(&working_dir).unwrap(); + panic!("test failed with error: {e}"); + }); + + let (scan_op_tx, scan_op_rx) = crossbeam_channel::unbounded(); + let (observer_command_tx, _observer_command_rx) = channel(); + + let _ = hiro_system_kit::thread_named("Stacks scan runloop") + .spawn(move || { + start_stacks_scan_runloop(&config, scan_op_rx, observer_command_tx.clone(), &ctx); + }) + .expect("unable to spawn thread"); + + let uuid = "test".to_string(); + let predicate_spec = StacksChainhookInstance { + uuid: uuid.clone(), + owner_uuid: None, + name: "idc".to_string(), + network: StacksNetwork::Devnet, + version: 0, + blocks: None, + start_block: Some(1), + end_block: Some(1_000), + expire_after_occurrence: None, + capture_all_events: None, + decode_clarity_values: None, + include_contract_abi: None, + predicate: StacksPredicate::BlockHeight(BlockIdentifierIndexRule::LowerThan(0)), + action: HookAction::Noop, + enabled: false, + expired_at: None, + }; + let op = StacksScanOp::StartScan { + predicate_spec, + unfinished_scan_data: None, + }; + let _ = scan_op_tx.send(op); + sleep(Duration::new(0, 500_000)); + let _ = scan_op_tx.send(StacksScanOp::KillScan(uuid)); + sleep(Duration::new(0, 500_000)); + // todo: currently the scanning runloop is a bit of a black box. we have no insight + // into what or how many predicates are being scanned. so for this test, there's no + // good way to determine if we successfully killed the scan. + // this [issue](https://github.com/hirosystems/chainhook/issues/509) will give us + // more data on these threads. When this is done we should update these tests + // to do some actual verification that the predicate is no longer being scanned + std::fs::remove_dir_all(&working_dir).unwrap(); +} + +#[tokio::test] +async fn test_stacks_bitcoin_kill_scan() { + let (_, _, _, _, bitcoin_rpc_port, _) = + setup_chainhook_service_ports().unwrap_or_else(|e| panic!("test failed with error: {e}")); + + let _ = hiro_system_kit::thread_named("Bitcoin rpc service") + .spawn(move || { + let future = mock_bitcoin_rpc(bitcoin_rpc_port, 1_000); + let _ = hiro_system_kit::nestable_block_on(future); + }) + .expect("unable to spawn thread"); + + sleep(Duration::new(1, 0)); + let mut config = Config::devnet_default(); + config.network.bitcoind_rpc_url = format!("http://0.0.0.0:{bitcoin_rpc_port}"); + + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + + let (scan_op_tx, scan_op_rx) = crossbeam_channel::unbounded(); + let (observer_command_tx, _observer_command_rx) = channel(); + + let _ = hiro_system_kit::thread_named("Stacks scan runloop") + .spawn(move || { + start_bitcoin_scan_runloop(&config, scan_op_rx, observer_command_tx.clone(), &ctx); + }) + .expect("unable to spawn thread"); + + let uuid = "test".to_string(); + let predicate_spec = BitcoinChainhookInstance { + uuid: uuid.clone(), + owner_uuid: None, + name: "idc".to_string(), + network: BitcoinNetwork::Regtest, + version: 0, + blocks: None, + start_block: Some(1), + end_block: Some(1_000), + expire_after_occurrence: None, + predicate: BitcoinPredicateType::Block, + action: HookAction::Noop, + enabled: false, + expired_at: None, + include_proof: false, + include_inputs: false, + include_outputs: false, + include_witness: false, + }; + + let op = BitcoinScanOp::StartScan { + predicate_spec, + unfinished_scan_data: None, + }; + let _ = scan_op_tx.send(op); + sleep(Duration::new(0, 50_000_000)); + let _ = scan_op_tx.send(BitcoinScanOp::KillScan(uuid)); + // todo: currently the scanning runloop is a bit of a black box. we have no insight + // into what or how many predicates are being scanned. so for this test, there's no + // good way to determine if we successfully killed the scan. + // this [issue](https://github.com/hirosystems/chainhook/issues/509) will give us + // more data on these threads. When this is done we should update these tests + // to do some actual verification that the predicate is no longer being scanned +} diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs index 33f1821b6..aef6db5d4 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs @@ -75,8 +75,8 @@ pub struct BitcoinChainhookSpecification { /// ``` /// You can deserialize the file to this type and create a [BitcoinChainhookInstance] for the desired network: /// ``` -/// use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookSpecificationNetworkMap; -/// use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookInstance; +/// use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookSpecificationNetworkMap; +/// use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookInstance; /// use chainhook_types::BitcoinNetwork; /// /// fn get_predicate(network: &BitcoinNetwork) -> Result { diff --git a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs index 939804a70..de5370422 100644 --- a/components/chainhook-sdk/src/chainhooks/stacks/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/stacks/mod.rs @@ -66,8 +66,8 @@ pub struct StacksChainhookSpecification { /// ``` /// You can deserialize the file to this type and create a [StacksChainhookInstance] for the desired network: /// ``` -/// use chainhook_sdk::chainhook::stacks::StacksChainhookSpecificationNetworkMap; -/// use chainhook_sdk::chainhook::stacks::StacksChainhookInstance; +/// use chainhook_sdk::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; +/// use chainhook_sdk::chainhooks::stacks::StacksChainhookInstance; /// use chainhook_types::StacksNetwork; /// /// fn get_predicate(network: &StacksNetwork) -> Result { @@ -90,7 +90,7 @@ pub struct StacksChainhookSpecificationNetworkMap { } impl StacksChainhookSpecificationNetworkMap { - pub fn into_specification_from_network( + pub fn into_specification_for_network( mut self, network: &StacksNetwork, ) -> Result { diff --git a/components/chainhook-sdk/src/chainhooks/types.rs b/components/chainhook-sdk/src/chainhooks/types.rs index c1d440478..dc965d9f4 100644 --- a/components/chainhook-sdk/src/chainhooks/types.rs +++ b/components/chainhook-sdk/src/chainhooks/types.rs @@ -33,7 +33,7 @@ impl ChainhookStore { ) -> Result { let spec = match hook { ChainhookSpecificationNetworkMap::Stacks(hook) => { - let spec = hook.into_specification_from_network(networks.1)?; + let spec = hook.into_specification_for_network(networks.1)?; self.stacks_chainhooks.push(spec.clone()); ChainhookInstance::Stacks(spec) } diff --git a/components/chainhook-sdk/src/observer/mod.rs b/components/chainhook-sdk/src/observer/mod.rs index d39aef22b..0b887acba 100644 --- a/components/chainhook-sdk/src/observer/mod.rs +++ b/components/chainhook-sdk/src/observer/mod.rs @@ -27,7 +27,7 @@ use bitcoincore_rpc::bitcoin::{BlockHash, Txid}; use bitcoincore_rpc::{Auth, Client, RpcApi}; use chainhook_types::{ BitcoinBlockData, BitcoinBlockSignaling, BitcoinChainEvent, BitcoinChainUpdatedWithBlocksData, - BitcoinChainUpdatedWithReorgData, BitcoinNetwork, BlockIdentifier, BlockchainEvent, + BitcoinChainUpdatedWithReorgData, BitcoinNetwork, BlockIdentifier, BlockchainEvent, Chain, StacksBlockData, StacksChainEvent, StacksNetwork, StacksNodeConfig, TransactionIdentifier, DEFAULT_STACKS_NODE_RPC, }; @@ -538,7 +538,7 @@ pub enum ObserverEvent { StacksChainEvent((StacksChainEvent, PredicateEvaluationReport)), NotifyBitcoinTransactionProxied, PredicateRegistered(ChainhookInstance), - PredicateDeregistered(String), + PredicateDeregistered(PredicateDeregisteredEvent), PredicateEnabled(ChainhookInstance), BitcoinPredicateTriggered(BitcoinChainhookOccurrencePayload), StacksPredicateTriggered(StacksChainhookOccurrencePayload), @@ -548,6 +548,12 @@ pub enum ObserverEvent { StacksChainMempoolEvent(StacksChainMempoolEvent), } +#[derive(Clone, Debug)] +pub struct PredicateDeregisteredEvent { + pub predicate_uuid: String, + pub chain: Chain, +} + #[derive(Debug, Clone, Deserialize, Serialize)] /// JSONRPC Request pub struct BitcoinRPCRequest { @@ -649,16 +655,25 @@ impl ObserverSidecar { /// /// ### Examples /// ``` -/// use chainhook_sdk::observer::EventObserver; +/// use chainhook_sdk::observer::EventObserverBuilder; +/// use chainhook_sdk::observer::EventObserverConfig; +/// use chainhook_sdk::observer::ObserverCommand; +/// use chainhook_sdk::utils::Context; +/// use std::error::Error; +/// use std::sync::mpsc::{Receiver, Sender}; /// -/// fn start_event_observer() -> Result<(), Box> { -/// EventObserver::new( +/// fn start_event_observer( +/// config: EventObserverConfig, +/// observer_commands_tx: &Sender, +/// observer_commands_rx: Receiver, +/// ctx: &Context, +/// )-> Result<(), Box> { +/// EventObserverBuilder::new( /// config, /// &observer_commands_tx, /// observer_commands_rx, /// &ctx /// ) -/// .stacks_startup_context(context) /// .start() /// } /// ``` @@ -1494,7 +1509,12 @@ pub async fn start_observer_commands_handler( prometheus_monitoring.btc_metrics_deregister_predicate(); } if let Some(ref tx) = observer_events_tx { - let _ = tx.send(ObserverEvent::PredicateDeregistered(hook_uuid.clone())); + let _ = tx.send(ObserverEvent::PredicateDeregistered( + PredicateDeregisteredEvent { + predicate_uuid: hook_uuid.clone(), + chain: Chain::Bitcoin, + }, + )); } } @@ -1671,7 +1691,12 @@ pub async fn start_observer_commands_handler( prometheus_monitoring.stx_metrics_deregister_predicate(); } if let Some(ref tx) = observer_events_tx { - let _ = tx.send(ObserverEvent::PredicateDeregistered(hook_uuid.clone())); + let _ = tx.send(ObserverEvent::PredicateDeregistered( + PredicateDeregisteredEvent { + predicate_uuid: hook_uuid.clone(), + chain: Chain::Stacks, + }, + )); } } @@ -1783,7 +1808,12 @@ pub async fn start_observer_commands_handler( }; // event if the predicate wasn't in the `chainhook_store`, propogate this event to delete from redis if let Some(tx) = &observer_events_tx { - let _ = tx.send(ObserverEvent::PredicateDeregistered(hook_uuid)); + let _ = tx.send(ObserverEvent::PredicateDeregistered( + PredicateDeregisteredEvent { + predicate_uuid: hook_uuid, + chain: Chain::Stacks, + }, + )); }; } ObserverCommand::DeregisterBitcoinPredicate(hook_uuid) => { @@ -1797,9 +1827,14 @@ pub async fn start_observer_commands_handler( // so only those that we find in the store should be removed prometheus_monitoring.btc_metrics_deregister_predicate(); }; - // event if the predicate wasn't in the `chainhook_store`, propogate this event to delete from redis + // even if the predicate wasn't in the `chainhook_store`, propogate this event to delete from redis if let Some(tx) = &observer_events_tx { - let _ = tx.send(ObserverEvent::PredicateDeregistered(hook_uuid)); + let _ = tx.send(ObserverEvent::PredicateDeregistered( + PredicateDeregisteredEvent { + predicate_uuid: hook_uuid.clone(), + chain: Chain::Bitcoin, + }, + )); }; } ObserverCommand::ExpireStacksPredicate(HookExpirationData { diff --git a/components/chainhook-sdk/src/observer/tests/mod.rs b/components/chainhook-sdk/src/observer/tests/mod.rs index 0eaa0ea8d..aee263086 100644 --- a/components/chainhook-sdk/src/observer/tests/mod.rs +++ b/components/chainhook-sdk/src/observer/tests/mod.rs @@ -20,6 +20,7 @@ use crate::indexer::tests::helpers::{ accounts, bitcoin_blocks, stacks_blocks, transactions::generate_test_tx_stacks_contract_call, }; use crate::monitoring::PrometheusMonitoring; +use crate::observer::PredicateDeregisteredEvent; use crate::observer::{ start_observer_commands_handler, EventObserverConfig, ObserverCommand, ObserverSidecar, }; @@ -168,7 +169,7 @@ fn generate_and_register_new_stacks_chainhook( ChainhookSpecificationNetworkMap::Stacks(chainhook.clone()), )); let mut chainhook = chainhook - .into_specification_from_network(&StacksNetwork::Devnet) + .into_specification_for_network(&StacksNetwork::Devnet) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( @@ -503,7 +504,10 @@ fn test_stacks_chainhook_register_deregister() { chainhook.uuid.clone(), )); assert!(match observer_events_rx.recv() { - Ok(ObserverEvent::PredicateDeregistered(deregistered_chainhook)) => { + Ok(ObserverEvent::PredicateDeregistered(PredicateDeregisteredEvent { + predicate_uuid: deregistered_chainhook, + .. + })) => { assert_eq!(chainhook.uuid, deregistered_chainhook); true } @@ -587,7 +591,7 @@ fn test_stacks_chainhook_auto_deregister() { ChainhookSpecificationNetworkMap::Stacks(chainhook.clone()), )); let mut chainhook = chainhook - .into_specification_from_network(&StacksNetwork::Devnet) + .into_specification_for_network(&StacksNetwork::Devnet) .unwrap(); chainhook.enabled = true; let _ = observer_commands_tx.send(ObserverCommand::EnablePredicate(ChainhookInstance::Stacks( @@ -692,7 +696,10 @@ fn test_stacks_chainhook_auto_deregister() { // Should signal that a hook was deregistered assert!(match observer_events_rx.recv() { - Ok(ObserverEvent::PredicateDeregistered(deregistered_hook)) => { + Ok(ObserverEvent::PredicateDeregistered(PredicateDeregisteredEvent { + predicate_uuid: deregistered_hook, + .. + })) => { assert_eq!(deregistered_hook, chainhook.uuid); true } @@ -858,7 +865,10 @@ fn test_bitcoin_chainhook_register_deregister() { chainhook.uuid.clone(), )); assert!(match observer_events_rx.recv() { - Ok(ObserverEvent::PredicateDeregistered(deregistered_chainhook)) => { + Ok(ObserverEvent::PredicateDeregistered(PredicateDeregisteredEvent { + predicate_uuid: deregistered_chainhook, + .. + })) => { assert_eq!(chainhook.uuid, deregistered_chainhook); true } @@ -1066,7 +1076,10 @@ fn test_bitcoin_chainhook_auto_deregister() { // Should signal that a hook was deregistered assert!(match observer_events_rx.recv() { - Ok(ObserverEvent::PredicateDeregistered(deregistered_hook)) => { + Ok(ObserverEvent::PredicateDeregistered(PredicateDeregisteredEvent { + predicate_uuid: deregistered_hook, + .. + })) => { assert_eq!(deregistered_hook, chainhook.uuid); true } diff --git a/components/chainhook-types-rs/src/lib.rs b/components/chainhook-types-rs/src/lib.rs index b5242d163..b015b6c75 100644 --- a/components/chainhook-types-rs/src/lib.rs +++ b/components/chainhook-types-rs/src/lib.rs @@ -18,6 +18,7 @@ pub use rosetta::*; pub const DEFAULT_STACKS_NODE_RPC: &str = "http://localhost:20443"; +#[derive(Clone, Debug)] pub enum Chain { Bitcoin, Stacks, diff --git a/docs/chainhook-openapi.json b/docs/chainhook-openapi.json index 770d9752e..1375554f7 100644 --- a/docs/chainhook-openapi.json +++ b/docs/chainhook-openapi.json @@ -157,7 +157,7 @@ "ChainhookSpecificationNetworkMap": { "oneOf": [ { - "description": "Maps some [BitcoinChainhookSpecification] to a corresponding [BitcoinNetwork]. This allows maintaining one serialized predicate file for a given predicate on each network.\n\n### Examples Given some file `predicate.json`: ```json { \"uuid\": \"my-id\", \"name\": \"My Predicate\", \"chain\": \"bitcoin\", \"version\": 1, \"networks\": { \"regtest\": { // ... }, \"testnet\": { // ... }, \"mainnet\": { // ... } } } ``` You can deserialize the file to this type and create a [BitcoinChainhookInstance] for the desired network: ``` use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookSpecificationNetworkMap; use chainhook_sdk::chainhook::bitcoin::BitcoinChainhookInstance; use chainhook_types::BitcoinNetwork;\n\nfn get_predicate(network: &BitcoinNetwork) -> Result { let json_predicate = std::fs::read_to_string(\"./predicate.json\").expect(\"Unable to read file\"); let hook_map: BitcoinChainhookSpecificationNetworkMap = serde_json::from_str(&json_predicate).expect(\"Unable to parse Chainhook map\"); hook_map.into_specification_for_network(network) }\n\n```", + "description": "Maps some [BitcoinChainhookSpecification] to a corresponding [BitcoinNetwork]. This allows maintaining one serialized predicate file for a given predicate on each network.\n\n### Examples Given some file `predicate.json`: ```json { \"uuid\": \"my-id\", \"name\": \"My Predicate\", \"chain\": \"bitcoin\", \"version\": 1, \"networks\": { \"regtest\": { // ... }, \"testnet\": { // ... }, \"mainnet\": { // ... } } } ``` You can deserialize the file to this type and create a [BitcoinChainhookInstance] for the desired network: ``` use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookSpecificationNetworkMap; use chainhook_sdk::chainhooks::bitcoin::BitcoinChainhookInstance; use chainhook_types::BitcoinNetwork;\n\nfn get_predicate(network: &BitcoinNetwork) -> Result { let json_predicate = std::fs::read_to_string(\"./predicate.json\").expect(\"Unable to read file\"); let hook_map: BitcoinChainhookSpecificationNetworkMap = serde_json::from_str(&json_predicate).expect(\"Unable to parse Chainhook map\"); hook_map.into_specification_for_network(network) }\n\n```", "type": "object", "required": [ "chain", @@ -239,7 +239,7 @@ } }, { - "description": "Maps some [StacksChainhookSpecification] to a corresponding [StacksNetwork]. This allows maintaining one serialized predicate file for a given predicate on each network.\n\n### Examples Given some file `predicate.json`: ```json { \"uuid\": \"my-id\", \"name\": \"My Predicate\", \"chain\": \"stacks\", \"version\": 1, \"networks\": { \"devnet\": { // ... }, \"testnet\": { // ... }, \"mainnet\": { // ... } } } ``` You can deserialize the file to this type and create a [StacksChainhookInstance] for the desired network: ``` use chainhook_sdk::chainhook::stacks::StacksChainhookSpecificationNetworkMap; use chainhook_sdk::chainhook::stacks::StacksChainhookInstance; use chainhook_types::StacksNetwork;\n\nfn get_predicate(network: &StacksNetwork) -> Result { let json_predicate = std::fs::read_to_string(\"./predicate.json\").expect(\"Unable to read file\"); let hook_map: StacksChainhookSpecificationNetworkMap = serde_json::from_str(&json_predicate).expect(\"Unable to parse Chainhook map\"); hook_map.into_specification_for_network(network) }\n\n```", + "description": "Maps some [StacksChainhookSpecification] to a corresponding [StacksNetwork]. This allows maintaining one serialized predicate file for a given predicate on each network.\n\n### Examples Given some file `predicate.json`: ```json { \"uuid\": \"my-id\", \"name\": \"My Predicate\", \"chain\": \"stacks\", \"version\": 1, \"networks\": { \"devnet\": { // ... }, \"testnet\": { // ... }, \"mainnet\": { // ... } } } ``` You can deserialize the file to this type and create a [StacksChainhookInstance] for the desired network: ``` use chainhook_sdk::chainhooks::stacks::StacksChainhookSpecificationNetworkMap; use chainhook_sdk::chainhooks::stacks::StacksChainhookInstance; use chainhook_types::StacksNetwork;\n\nfn get_predicate(network: &StacksNetwork) -> Result { let json_predicate = std::fs::read_to_string(\"./predicate.json\").expect(\"Unable to read file\"); let hook_map: StacksChainhookSpecificationNetworkMap = serde_json::from_str(&json_predicate).expect(\"Unable to parse Chainhook map\"); hook_map.into_specification_for_network(network) }\n\n```", "type": "object", "required": [ "chain", From 59c8e1a63f78fc00d2b998bc9c89ce9f2218187a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 27 Jun 2024 10:12:29 -0400 Subject: [PATCH 28/28] apply suggestion from review --- components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs index aef6db5d4..143917a7d 100644 --- a/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs +++ b/components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs @@ -319,11 +319,11 @@ pub fn get_canonical_pox_config(network: &BitcoinNetwork) -> PoxConfig { #[derive(Debug, Clone, PartialEq)] #[repr(u8)] pub enum StacksOpcodes { - BlockCommit = '[' as u8, - KeyRegister = '^' as u8, - StackStx = 'x' as u8, - PreStx = 'p' as u8, - TransferStx = '$' as u8, + BlockCommit = b'[', + KeyRegister = b'^', + StackStx = b'x', + PreStx = b'p', + TransferStx = b'$', } impl TryFrom for StacksOpcodes {