Skip to content

Commit

Permalink
feat: ability to control inclusion of inputs/outputs/proofs/witness
Browse files Browse the repository at this point in the history
  • Loading branch information
Ludo Galabru committed Apr 13, 2023
1 parent 1a433e5 commit daf5547
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 91 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ Additional configuration knobs available:
// Stop evaluating chainhook after a given number of occurrences found:
"expire_after_occurrence": 1

// Include proof:
"include_proof": false

// Include Bitcoin transaction inputs in payload:
"include_inputs": false

// Include Bitcoin transaction outputs in payload:
"include_outputs": false

// Include Bitcoin transaction witness in payload:
"include_witness": false

```

Putting all the pieces together:
Expand Down
4 changes: 4 additions & 0 deletions components/chainhook-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
action: HookAction::FileAppend(FileHook {
path: "ordinals.txt".into(),
}),
include_inputs: None,
include_outputs: None,
include_proof: None,
include_witness: None,
},
);

Expand Down
9 changes: 6 additions & 3 deletions components/chainhook-cli/src/scan/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,12 @@ pub async fn execute_predicates_action<'a>(
ctx: &Context,
) -> Result<u32, ()> {
let mut actions_triggered = 0;
let proofs = gather_proofs(&hits, &config, &ctx);
for hit in hits.into_iter() {
match handle_bitcoin_hook_action(hit, &proofs) {
let mut proofs = HashMap::new();
for trigger in hits.into_iter() {
if trigger.chainhook.include_proof {
gather_proofs(&trigger, &mut proofs, &config, &ctx);
}
match handle_bitcoin_hook_action(trigger, &proofs) {
Err(e) => {
error!(ctx.expect_logger(), "unable to handle action {}", e);
}
Expand Down
4 changes: 1 addition & 3 deletions components/chainhook-cli/src/scan/stacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ use crate::{
config::Config,
};
use chainhook_event_observer::{
chainhooks::{
stacks::evaluate_stacks_chainhook_on_blocks,
},
chainhooks::stacks::evaluate_stacks_chainhook_on_blocks,
indexer::{self, stacks::standardize_stacks_serialized_block_header, Indexer},
utils::Context,
};
Expand Down
9 changes: 2 additions & 7 deletions components/chainhook-cli/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@ use crate::scan::stacks::scan_stacks_chainstate_via_csv_using_predicate;

use chainhook_event_observer::chainhooks::types::{ChainhookConfig, ChainhookFullSpecification};

use chainhook_event_observer::chainhooks::types::ChainhookSpecification;
use chainhook_event_observer::observer::{start_event_observer, ApiKey, ObserverEvent};
use chainhook_event_observer::utils::Context;
use chainhook_event_observer::{
chainhooks::types::ChainhookSpecification,
};
use chainhook_types::{
BitcoinBlockSignaling, StacksBlockData, StacksChainEvent,
};
use chainhook_types::{BitcoinBlockSignaling, StacksBlockData, StacksChainEvent};
use redis::{Commands, Connection};


use std::sync::mpsc::channel;

pub const DEFAULT_INGESTION_PORT: u16 = 20455;
Expand Down
55 changes: 38 additions & 17 deletions components/chainhook-event-observer/src/chainhooks/bitcoin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ pub fn serialize_bitcoin_payload_to_json<'a>(
trigger: BitcoinTriggerChainhook<'a>,
proofs: &HashMap<&'a TransactionIdentifier, String>,
) -> JsonValue {
let predicate = &trigger.chainhook.predicate;
let predicate_spec = &trigger.chainhook;
json!({
"apply": trigger.apply.into_iter().map(|(transactions, block)| {
json!({
"block_identifier": block.block_identifier,
"parent_block_identifier": block.parent_block_identifier,
"timestamp": block.timestamp,
"transactions": serialize_bitcoin_transactions_to_json(&predicate, &transactions, proofs),
"transactions": serialize_bitcoin_transactions_to_json(&predicate_spec, &transactions, proofs),
"metadata": block.metadata,
})
}).collect::<Vec<_>>(),
Expand All @@ -146,7 +146,7 @@ pub fn serialize_bitcoin_payload_to_json<'a>(
"block_identifier": block.block_identifier,
"parent_block_identifier": block.parent_block_identifier,
"timestamp": block.timestamp,
"transactions": serialize_bitcoin_transactions_to_json(&predicate, &transactions, proofs),
"transactions": serialize_bitcoin_transactions_to_json(&predicate_spec, &transactions, proofs),
"metadata": block.metadata,
})
}).collect::<Vec<_>>(),
Expand All @@ -158,36 +158,57 @@ pub fn serialize_bitcoin_payload_to_json<'a>(
}

pub fn serialize_bitcoin_transactions_to_json<'a>(
predicate: &BitcoinPredicateType,
predicate_spec: &BitcoinChainhookSpecification,
transactions: &Vec<&BitcoinTransactionData>,
proofs: &HashMap<&'a TransactionIdentifier, String>,
) -> Vec<JsonValue> {
transactions.into_iter().map(|transaction| {
transactions
.into_iter()
.map(|transaction| {
let mut metadata = serde_json::Map::new();
if predicate.include_inputs() {
metadata.insert("inputs".into(), json!(transaction.metadata.inputs.iter().map(|input| {
json!({
"txin": format!("0x{}:{}", input.previous_output.txid, input.previous_output.vout),
"sequence": input.sequence,
})
}).collect::<Vec<_>>()));
if predicate_spec.include_inputs {
metadata.insert(
"inputs".into(),
json!(transaction
.metadata
.inputs
.iter()
.map(|input| {
json!({
"txin": format!("0x{}", input.previous_output.txid),
"vout": input.previous_output.vout,
"sequence": input.sequence,
})
})
.collect::<Vec<_>>()),
);
}
if predicate.include_outputs() {
if predicate_spec.include_outputs {
metadata.insert("outputs".into(), json!(transaction.metadata.outputs));
}
if !transaction.metadata.stacks_operations.is_empty() {
metadata.insert("stacks_operations".into(), json!(transaction.metadata.stacks_operations));
metadata.insert(
"stacks_operations".into(),
json!(transaction.metadata.stacks_operations),
);
}
if !transaction.metadata.ordinal_operations.is_empty() {
metadata.insert("ordinal_operations".into(), json!(transaction.metadata.ordinal_operations));
metadata.insert(
"ordinal_operations".into(),
json!(transaction.metadata.ordinal_operations),
);
}
metadata.insert("proof".into(), json!(proofs.get(&transaction.transaction_identifier)));
metadata.insert(
"proof".into(),
json!(proofs.get(&transaction.transaction_identifier)),
);
json!({
"transaction_identifier": transaction.transaction_identifier,
"operations": transaction.operations,
"metadata": metadata
})
}).collect::<Vec<_>>()
})
.collect::<Vec<_>>()
}

pub fn handle_bitcoin_hook_action<'a>(
Expand Down
50 changes: 16 additions & 34 deletions components/chainhook-event-observer/src/chainhooks/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ pub struct BitcoinChainhookSpecification {
pub expire_after_occurrence: Option<u64>,
pub predicate: BitcoinPredicateType,
pub action: HookAction,
pub include_proof: bool,
pub include_inputs: bool,
pub include_outputs: bool,
pub include_witness: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
Expand Down Expand Up @@ -283,6 +287,10 @@ impl BitcoinChainhookFullSpecification {
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),
})
}
}
Expand All @@ -295,6 +303,14 @@ pub struct BitcoinChainhookNetworkSpecification {
pub end_block: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_after_occurrence: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_proof: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_inputs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_outputs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_witness: Option<bool>,
#[serde(rename = "if_this")]
pub predicate: BitcoinPredicateType,
#[serde(rename = "then_that")]
Expand Down Expand Up @@ -456,40 +472,6 @@ pub enum BitcoinPredicateType {
Protocol(Protocols),
}

impl BitcoinPredicateType {
pub fn include_inputs(&self) -> bool {
match &self {
BitcoinPredicateType::Block => true,
BitcoinPredicateType::Txid(_rules) => true,
BitcoinPredicateType::Inputs(_rules) => true,
BitcoinPredicateType::Outputs(_rules) => false,
BitcoinPredicateType::Protocol(Protocols::Ordinal(_)) => false,
BitcoinPredicateType::Protocol(Protocols::Stacks(_)) => false,
}
}

pub fn include_outputs(&self) -> bool {
match &self {
BitcoinPredicateType::Block => true,
BitcoinPredicateType::Txid(_rules) => true,
BitcoinPredicateType::Inputs(_rules) => false,
BitcoinPredicateType::Outputs(_rules) => true,
BitcoinPredicateType::Protocol(Protocols::Ordinal(_)) => true,
BitcoinPredicateType::Protocol(Protocols::Stacks(_)) => false,
}
}

pub fn include_witness(&self) -> bool {
match &self {
BitcoinPredicateType::Block => true,
BitcoinPredicateType::Txid(_rules) => true,
BitcoinPredicateType::Inputs(_rules) => false,
BitcoinPredicateType::Outputs(_rules) => false,
BitcoinPredicateType::Protocol(_rules) => false,
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum InputPredicate {
Expand Down
57 changes: 30 additions & 27 deletions components/chainhook-event-observer/src/observer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,10 +536,11 @@ pub fn apply_bitcoin_block() {}
pub fn rollback_bitcoin_block() {}

pub fn gather_proofs<'a>(
chainhooks_to_trigger: &Vec<BitcoinTriggerChainhook<'a>>,
trigger: &BitcoinTriggerChainhook<'a>,
proofs: &mut HashMap<&'a TransactionIdentifier, String>,
config: &EventObserverConfig,
ctx: &Context,
) -> HashMap<&'a TransactionIdentifier, String> {
) {
let bitcoin_client_rpc = Client::new(
&config.bitcoind_rpc_url,
Auth::UserPass(
Expand All @@ -549,35 +550,31 @@ pub fn gather_proofs<'a>(
)
.expect("unable to build http client");

let mut proofs = HashMap::new();
for hook_to_trigger in chainhooks_to_trigger.iter() {
for (transactions, block) in hook_to_trigger.apply.iter() {
for transaction in transactions.iter() {
if !proofs.contains_key(&transaction.transaction_identifier) {
ctx.try_log(|logger| {
slog::info!(
logger,
"Collecting proof for transaction {}",
transaction.transaction_identifier.hash
)
});
match get_bitcoin_proof(
&bitcoin_client_rpc,
&transaction.transaction_identifier,
&block.block_identifier,
) {
Ok(proof) => {
proofs.insert(&transaction.transaction_identifier, proof);
}
Err(e) => {
ctx.try_log(|logger| slog::error!(logger, "{e}"));
}
for (transactions, block) in trigger.apply.iter() {
for transaction in transactions.iter() {
if !proofs.contains_key(&transaction.transaction_identifier) {
ctx.try_log(|logger| {
slog::info!(
logger,
"Collecting proof for transaction {}",
transaction.transaction_identifier.hash
)
});
match get_bitcoin_proof(
&bitcoin_client_rpc,
&transaction.transaction_identifier,
&block.block_identifier,
) {
Ok(proof) => {
proofs.insert(&transaction.transaction_identifier, proof);
}
Err(e) => {
ctx.try_log(|logger| slog::error!(logger, "{e}"));
}
}
}
}
}
proofs
}

pub async fn start_observer_commands_handler(
Expand Down Expand Up @@ -939,7 +936,13 @@ pub async fn start_observer_commands_handler(
}
}

let proofs = gather_proofs(&chainhooks_to_trigger, &config, &ctx);
let mut proofs = HashMap::new();
for trigger in chainhooks_to_trigger.iter() {
if trigger.chainhook.include_proof {
gather_proofs(&trigger, &mut proofs, &config, &ctx);
}
}

ctx.try_log(|logger| {
slog::info!(
logger,
Expand Down
4 changes: 4 additions & 0 deletions components/chainhook-event-observer/src/observer/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ fn bitcoin_chainhook_p2pkh(
ExactMatchingRule::Equals(address.to_string()),
)),
action: HookAction::Noop,
include_proof: None,
include_inputs: None,
include_outputs: None,
include_witness: None,
},
);

Expand Down

0 comments on commit daf5547

Please sign in to comment.