Skip to content

Commit

Permalink
feat: improve chainhook-sdk interface (#608)
Browse files Browse the repository at this point in the history
### Description
The goal of this PR is to make it much easier to use the Chainhook SDK.
Previously, there were many fields that are rarely needed for the
average user which had to be set when configuring the SDK. Many of these
fields had confusing names that made it difficult to know how they were
used in the SDK. Additionally, many of these fields were only needed for
observing stacks events, but bitcoin-only users had to specify them
anyways.

This has a few major changes to the Chainhook SDK:
- Removing some unused fields from the event observer config
(`cache_path`, `data_handler_tx`, and`ingestion_port`)
(694fb4d)
- Renames `display_logs` -> `display_stacks_ingestion_logs`
(694fb4d)
- Renames `EventObserverConfigOverrides` ->
`StacksEventObserverConfigBuilder`
(9da1178)
- Renames `ingestion_port` -> `chainhook_stacks_block_ingestion_port`
for `StacksEventObserverConfigBuilder`
(4e997fc)
- Adds a `BitcoinEventObserverConfigBuilder`
(fc67dff)
- Renames some very confusingly named structs
(5a4cb39):
- `*ChainhookFullSpecification` => `*ChainhookSpecificationNetworkMap`
   - `*ChainhookNetworkSpecification` => `*ChainhookSpecification`
   - `*ChainhookSpecification` => `*ChainhookInstance`
- refactor: moves stacks/bitcoin specific types to their respective
types folder (83e8336)
- adds helpers for registering chainhooks
(4debc28)
- renames `ChainhookConfig` -> `ChainhookStore`
(c54b6e7)
- add `EventObserverBuilder` to make a clean interface for starting an
event observer (fe04dd9)
 - add a bunch of rsdoc comments with examples

#### Breaking change?

This will break some aspects of the Chainhook SDK. It should be a simple
upgrade:
- If you're building any of the above structs directly, rename the
fields accordingly
- If you're using `::new(..)` to build any of the above structs with
fields that are removed, you may need to remove some fields
 - You can probably remove a good bit of code by using the builders

### Example

New code example to start a bitcoin event observer:
```rust

fn start_observer(ctx: &Context) -> Result<(), String> {
    let json_predicate = std::fs::read_to_string("./predicate.json").expect("Unable to read file");

    let hook_instance: BitcoinChainhookInstance =
        serde_json::from_str(&json_predicate).expect("unable to parse chainhook spec");

    let config = BitcoinEventObserverConfigBuilder::new()
        .rpc_username("regtest")
        .rpc_password("test1235")
        .rpc_url("http://0.0.0.0:8332")
        .finish()?
        .register_bitcoin_chainhook_instance(hook_instance)?
        .to_owned();

    let (observer_commands_tx, observer_commands_rx) = channel();

    EventObserverBuilder::new(config, &observer_commands_tx, observer_commands_rx, &ctx)
        .start()
        .map_err(|e| format!("event observer error: {}", e.to_string()))
}
```

<details>
  <summary>Previous usage of starting a bitcoin observer</summary>

```rust 

    let json_predicate = std::fs::read_to_string("./predicate.json").expect("Unable to read file");

    let hook_spec: BitcoinChainhookFullSpecification =
        serde_json::from_str(&json_predicate).expect("unable to parse chainhook spec");

    let bitcoin_network = BitcoinNetwork::Regtest;
    let stacks_network = chainhook_sdk::types::StacksNetwork::Mainnet;
    let mut bitcoin_hook_spec = hook_spec
        .into_selected_network_specification(&bitcoin_network)
        .expect("unable to parse bitcoin spec");
    bitcoin_hook_spec.enabled = true;

    let mut chainhook_config = ChainhookConfig::new();
    chainhook_config
        .register_specification(ChainhookSpecification::Bitcoin(bitcoin_hook_spec))
        .expect("failed to register chainhook spec");

    let config = EventObserverConfig {
        chainhook_config: Some(chainhook_config),
        bitcoin_rpc_proxy_enabled: false,
        ingestion_port: 0,
        bitcoind_rpc_username: "regtest".to_string(),
        bitcoind_rpc_password: "test1235".to_string(),
        bitcoind_rpc_url: "http://0.0.0.0:8332".to_string(),
        bitcoin_block_signaling: BitcoinBlockSignaling::ZeroMQ("tcp://0.0.0.0:18543".to_string()),
        display_logs: true,
        cache_path: String::new(),
        bitcoin_network: bitcoin_network,
        stacks_network: stacks_network,
        data_handler_tx: None,
        prometheus_monitoring_port: None,
    };
    let (observer_commands_tx, observer_commands_rx) = channel();

    // set up context to configure how we display logs from the event observer
    let logger = hiro_system_kit::log::setup_logger();
    let _guard = hiro_system_kit::log::setup_global_logger(logger.clone());
    let ctx = chainhook_sdk::utils::Context {
        logger: Some(logger),
        tracer: false,
    };

    let moved_ctx = ctx.clone();
    let _ = hiro_system_kit::thread_named("Chainhook event observer")
        .spawn(move || {
            let future = start_bitcoin_event_observer(
                config,
                observer_commands_tx,
                observer_commands_rx,
                None,
                None,
                moved_ctx,
            );
            match hiro_system_kit::nestable_block_on(future) {
                Ok(_) => {}
                Err(e) => {
                    println!("{}", e)
                }
            }
        })
        .expect("unable to spawn thread");
```

</details>

Fixes #598
  • Loading branch information
MicaiahReid authored Jul 1, 2024
1 parent bb2c99d commit 3c347a2
Show file tree
Hide file tree
Showing 22 changed files with 1,213 additions and 853 deletions.
79 changes: 43 additions & 36 deletions components/chainhook-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinPredicateType,
ChainhookFullSpecification, FileHook, HookAction, InscriptionFeedData, OrdinalOperations,
StacksChainhookFullSpecification, StacksChainhookNetworkSpecification, 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};
Expand Down Expand Up @@ -351,7 +354,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
.predicates_paths
.iter()
.map(|p| load_predicate_from_path(p))
.collect::<Result<Vec<ChainhookFullSpecification>, _>>()?;
.collect::<Result<Vec<ChainhookSpecificationNetworkMap>, _>>()?;

info!(ctx.expect_logger(), "Starting service...",);

Expand Down Expand Up @@ -384,7 +387,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,
Expand All @@ -401,7 +404,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,
Expand All @@ -418,20 +421,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,
Expand All @@ -451,13 +456,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());
Expand Down Expand Up @@ -500,9 +507,9 @@ 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)
.into_specification_for_network(&config.network.bitcoin_network)
{
Ok(predicate) => predicate,
Err(e) => {
Expand All @@ -522,9 +529,9 @@ 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)
.into_specification_for_network(&config.network.stacks_network)
{
Ok(predicate) => predicate,
Err(e) => {
Expand Down Expand Up @@ -569,13 +576,13 @@ 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)
.into_specification_for_network(&config.network.bitcoin_network)
{
Ok(predicate) => predicate,
Err(e) => {
Expand All @@ -586,9 +593,9 @@ 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)
.into_specification_for_network(&config.network.stacks_network)
{
Ok(predicate) => predicate,
Err(e) => {
Expand Down Expand Up @@ -866,15 +873,15 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {

pub fn load_predicate_from_path(
predicate_path: &str,
) -> Result<ChainhookFullSpecification, String> {
) -> Result<ChainhookSpecificationNetworkMap, String> {
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);
let mut file_buffer = vec![];
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)
}
Expand Down
8 changes: 3 additions & 5 deletions components/chainhook-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -114,17 +115,14 @@ impl Config {
pub fn get_event_observer_config(&self) -> EventObserverConfig {
EventObserverConfig {
bitcoin_rpc_proxy_enabled: true,
chainhook_config: None,
ingestion_port: DEFAULT_INGESTION_PORT,
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(),
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,
}
}
Expand Down
6 changes: 3 additions & 3 deletions components/chainhook-cli/src/scan/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::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,
Expand All @@ -27,7 +27,7 @@ use std::sync::{Arc, RwLock};
use super::common::PredicateScanResult;

pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate(
predicate_spec: &BitcoinChainhookSpecification,
predicate_spec: &BitcoinChainhookInstance,
unfinished_scan_data: Option<ScanningData>,
config: &Config,
kill_signal: Option<Arc<RwLock<bool>>>,
Expand Down Expand Up @@ -265,7 +265,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<u32, String> {
Expand Down
10 changes: 5 additions & 5 deletions components/chainhook-cli/src/scan/stacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ use chainhook_sdk::{
utils::Context,
};
use chainhook_sdk::{
chainhooks::{
stacks::{handle_stacks_hook_action, StacksChainhookOccurrence, StacksTriggerChainhook},
types::StacksChainhookSpecification,
chainhooks::stacks::{
handle_stacks_hook_action, StacksChainhookInstance, StacksChainhookOccurrence,
StacksTriggerChainhook,
},
utils::{file_append, send_request, AbstractStacksBlock},
};
Expand Down Expand Up @@ -171,7 +171,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<ScanningData>,
stacks_db_conn: &DB,
config: &Config,
Expand Down Expand Up @@ -441,7 +441,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<BlockIdentifier, String> {
Expand Down
24 changes: 12 additions & 12 deletions components/chainhook-cli/src/service/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

use chainhook_sdk::{
chainhooks::types::{ChainhookFullSpecification, ChainhookSpecification},
chainhooks::types::{ChainhookSpecificationNetworkMap, ChainhookInstance},
observer::ObserverCommand,
utils::Context,
};
Expand Down Expand Up @@ -120,7 +120,7 @@ fn handle_get_predicates(
#[openapi(tag = "Managing Predicates")]
#[post("/v1/chainhooks", format = "application/json", data = "<predicate>")]
fn handle_create_predicate(
predicate: Result<Json<ChainhookFullSpecification>, rocket::serde::json::Error>,
predicate: Result<Json<ChainhookSpecificationNetworkMap>, rocket::serde::json::Error>,
api_config: &State<PredicatesApiConfig>,
background_job_tx: &State<Arc<Mutex<Sender<ObserverCommand>>>>,
ctx: &State<Context>,
Expand Down Expand Up @@ -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,
) {
Expand Down Expand Up @@ -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,
) {
Expand Down Expand Up @@ -281,7 +281,7 @@ pub fn get_entry_from_predicates_db(
predicate_key: &str,
predicate_db_conn: &mut Connection,
_ctx: &Context,
) -> Result<Option<(ChainhookSpecification, PredicateStatus)>, String> {
) -> Result<Option<(ChainhookInstance, PredicateStatus)>, String> {
let entry: HashMap<String, String> = predicate_db_conn.hgetall(predicate_key).map_err(|e| {
format!(
"unable to load chainhook associated with key {}: {}",
Expand All @@ -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!(
Expand All @@ -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<Vec<(ChainhookSpecification, PredicateStatus)>, String> {
) -> Result<Vec<(ChainhookInstance, PredicateStatus)>, String> {
let chainhooks_to_load: Vec<String> = 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();
Expand Down Expand Up @@ -349,7 +349,7 @@ pub fn get_entries_from_predicates_db(
pub fn load_predicates_from_redis(
config: &crate::config::Config,
ctx: &Context,
) -> Result<Vec<(ChainhookSpecification, PredicateStatus)>, String> {
) -> Result<Vec<(ChainhookInstance, PredicateStatus)>, 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()))?;
Expand Down Expand Up @@ -378,19 +378,19 @@ pub fn get_routes_spec() -> (Vec<rocket::Route>, 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,
"predicate": spec.predicate,
"status": status,
"enabled": spec.enabled,
}),
(ChainhookSpecification::Bitcoin(spec), status) => json!({
(ChainhookInstance::Bitcoin(spec), status) => json!({
"chain": "bitcoin",
"uuid": spec.uuid,
"network": spec.network,
Expand Down
Loading

0 comments on commit 3c347a2

Please sign in to comment.