Skip to content

Commit daf5547

Browse files
author
Ludo Galabru
committed
feat: ability to control inclusion of inputs/outputs/proofs/witness
1 parent 1a433e5 commit daf5547

File tree

9 files changed

+113
-91
lines changed

9 files changed

+113
-91
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,18 @@ Additional configuration knobs available:
217217
// Stop evaluating chainhook after a given number of occurrences found:
218218
"expire_after_occurrence": 1
219219

220+
// Include proof:
221+
"include_proof": false
222+
223+
// Include Bitcoin transaction inputs in payload:
224+
"include_inputs": false
225+
226+
// Include Bitcoin transaction outputs in payload:
227+
"include_outputs": false
228+
229+
// Include Bitcoin transaction witness in payload:
230+
"include_witness": false
231+
220232
```
221233

222234
Putting all the pieces together:

components/chainhook-cli/src/cli/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
470470
action: HookAction::FileAppend(FileHook {
471471
path: "ordinals.txt".into(),
472472
}),
473+
include_inputs: None,
474+
include_outputs: None,
475+
include_proof: None,
476+
include_witness: None,
473477
},
474478
);
475479

components/chainhook-cli/src/scan/bitcoin.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,12 @@ pub async fn execute_predicates_action<'a>(
272272
ctx: &Context,
273273
) -> Result<u32, ()> {
274274
let mut actions_triggered = 0;
275-
let proofs = gather_proofs(&hits, &config, &ctx);
276-
for hit in hits.into_iter() {
277-
match handle_bitcoin_hook_action(hit, &proofs) {
275+
let mut proofs = HashMap::new();
276+
for trigger in hits.into_iter() {
277+
if trigger.chainhook.include_proof {
278+
gather_proofs(&trigger, &mut proofs, &config, &ctx);
279+
}
280+
match handle_bitcoin_hook_action(trigger, &proofs) {
278281
Err(e) => {
279282
error!(ctx.expect_logger(), "unable to handle action {}", e);
280283
}

components/chainhook-cli/src/scan/stacks.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ use crate::{
99
config::Config,
1010
};
1111
use chainhook_event_observer::{
12-
chainhooks::{
13-
stacks::evaluate_stacks_chainhook_on_blocks,
14-
},
12+
chainhooks::stacks::evaluate_stacks_chainhook_on_blocks,
1513
indexer::{self, stacks::standardize_stacks_serialized_block_header, Indexer},
1614
utils::Context,
1715
};

components/chainhook-cli/src/service/mod.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@ use crate::scan::stacks::scan_stacks_chainstate_via_csv_using_predicate;
44

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

7+
use chainhook_event_observer::chainhooks::types::ChainhookSpecification;
78
use chainhook_event_observer::observer::{start_event_observer, ApiKey, ObserverEvent};
89
use chainhook_event_observer::utils::Context;
9-
use chainhook_event_observer::{
10-
chainhooks::types::ChainhookSpecification,
11-
};
12-
use chainhook_types::{
13-
BitcoinBlockSignaling, StacksBlockData, StacksChainEvent,
14-
};
10+
use chainhook_types::{BitcoinBlockSignaling, StacksBlockData, StacksChainEvent};
1511
use redis::{Commands, Connection};
1612

17-
1813
use std::sync::mpsc::channel;
1914

2015
pub const DEFAULT_INGESTION_PORT: u16 = 20455;

components/chainhook-event-observer/src/chainhooks/bitcoin/mod.rs

+38-17
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,14 @@ pub fn serialize_bitcoin_payload_to_json<'a>(
130130
trigger: BitcoinTriggerChainhook<'a>,
131131
proofs: &HashMap<&'a TransactionIdentifier, String>,
132132
) -> JsonValue {
133-
let predicate = &trigger.chainhook.predicate;
133+
let predicate_spec = &trigger.chainhook;
134134
json!({
135135
"apply": trigger.apply.into_iter().map(|(transactions, block)| {
136136
json!({
137137
"block_identifier": block.block_identifier,
138138
"parent_block_identifier": block.parent_block_identifier,
139139
"timestamp": block.timestamp,
140-
"transactions": serialize_bitcoin_transactions_to_json(&predicate, &transactions, proofs),
140+
"transactions": serialize_bitcoin_transactions_to_json(&predicate_spec, &transactions, proofs),
141141
"metadata": block.metadata,
142142
})
143143
}).collect::<Vec<_>>(),
@@ -146,7 +146,7 @@ pub fn serialize_bitcoin_payload_to_json<'a>(
146146
"block_identifier": block.block_identifier,
147147
"parent_block_identifier": block.parent_block_identifier,
148148
"timestamp": block.timestamp,
149-
"transactions": serialize_bitcoin_transactions_to_json(&predicate, &transactions, proofs),
149+
"transactions": serialize_bitcoin_transactions_to_json(&predicate_spec, &transactions, proofs),
150150
"metadata": block.metadata,
151151
})
152152
}).collect::<Vec<_>>(),
@@ -158,36 +158,57 @@ pub fn serialize_bitcoin_payload_to_json<'a>(
158158
}
159159

160160
pub fn serialize_bitcoin_transactions_to_json<'a>(
161-
predicate: &BitcoinPredicateType,
161+
predicate_spec: &BitcoinChainhookSpecification,
162162
transactions: &Vec<&BitcoinTransactionData>,
163163
proofs: &HashMap<&'a TransactionIdentifier, String>,
164164
) -> Vec<JsonValue> {
165-
transactions.into_iter().map(|transaction| {
165+
transactions
166+
.into_iter()
167+
.map(|transaction| {
166168
let mut metadata = serde_json::Map::new();
167-
if predicate.include_inputs() {
168-
metadata.insert("inputs".into(), json!(transaction.metadata.inputs.iter().map(|input| {
169-
json!({
170-
"txin": format!("0x{}:{}", input.previous_output.txid, input.previous_output.vout),
171-
"sequence": input.sequence,
172-
})
173-
}).collect::<Vec<_>>()));
169+
if predicate_spec.include_inputs {
170+
metadata.insert(
171+
"inputs".into(),
172+
json!(transaction
173+
.metadata
174+
.inputs
175+
.iter()
176+
.map(|input| {
177+
json!({
178+
"txin": format!("0x{}", input.previous_output.txid),
179+
"vout": input.previous_output.vout,
180+
"sequence": input.sequence,
181+
})
182+
})
183+
.collect::<Vec<_>>()),
184+
);
174185
}
175-
if predicate.include_outputs() {
186+
if predicate_spec.include_outputs {
176187
metadata.insert("outputs".into(), json!(transaction.metadata.outputs));
177188
}
178189
if !transaction.metadata.stacks_operations.is_empty() {
179-
metadata.insert("stacks_operations".into(), json!(transaction.metadata.stacks_operations));
190+
metadata.insert(
191+
"stacks_operations".into(),
192+
json!(transaction.metadata.stacks_operations),
193+
);
180194
}
181195
if !transaction.metadata.ordinal_operations.is_empty() {
182-
metadata.insert("ordinal_operations".into(), json!(transaction.metadata.ordinal_operations));
196+
metadata.insert(
197+
"ordinal_operations".into(),
198+
json!(transaction.metadata.ordinal_operations),
199+
);
183200
}
184-
metadata.insert("proof".into(), json!(proofs.get(&transaction.transaction_identifier)));
201+
metadata.insert(
202+
"proof".into(),
203+
json!(proofs.get(&transaction.transaction_identifier)),
204+
);
185205
json!({
186206
"transaction_identifier": transaction.transaction_identifier,
187207
"operations": transaction.operations,
188208
"metadata": metadata
189209
})
190-
}).collect::<Vec<_>>()
210+
})
211+
.collect::<Vec<_>>()
191212
}
192213

193214
pub fn handle_bitcoin_hook_action<'a>(

components/chainhook-event-observer/src/chainhooks/types.rs

+16-34
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ pub struct BitcoinChainhookSpecification {
217217
pub expire_after_occurrence: Option<u64>,
218218
pub predicate: BitcoinPredicateType,
219219
pub action: HookAction,
220+
pub include_proof: bool,
221+
pub include_inputs: bool,
222+
pub include_outputs: bool,
223+
pub include_witness: bool,
220224
}
221225

222226
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
@@ -283,6 +287,10 @@ impl BitcoinChainhookFullSpecification {
283287
expire_after_occurrence: spec.expire_after_occurrence,
284288
predicate: spec.predicate,
285289
action: spec.action,
290+
include_proof: spec.include_proof.unwrap_or(false),
291+
include_inputs: spec.include_inputs.unwrap_or(false),
292+
include_outputs: spec.include_outputs.unwrap_or(false),
293+
include_witness: spec.include_witness.unwrap_or(false),
286294
})
287295
}
288296
}
@@ -295,6 +303,14 @@ pub struct BitcoinChainhookNetworkSpecification {
295303
pub end_block: Option<u64>,
296304
#[serde(skip_serializing_if = "Option::is_none")]
297305
pub expire_after_occurrence: Option<u64>,
306+
#[serde(skip_serializing_if = "Option::is_none")]
307+
pub include_proof: Option<bool>,
308+
#[serde(skip_serializing_if = "Option::is_none")]
309+
pub include_inputs: Option<bool>,
310+
#[serde(skip_serializing_if = "Option::is_none")]
311+
pub include_outputs: Option<bool>,
312+
#[serde(skip_serializing_if = "Option::is_none")]
313+
pub include_witness: Option<bool>,
298314
#[serde(rename = "if_this")]
299315
pub predicate: BitcoinPredicateType,
300316
#[serde(rename = "then_that")]
@@ -456,40 +472,6 @@ pub enum BitcoinPredicateType {
456472
Protocol(Protocols),
457473
}
458474

459-
impl BitcoinPredicateType {
460-
pub fn include_inputs(&self) -> bool {
461-
match &self {
462-
BitcoinPredicateType::Block => true,
463-
BitcoinPredicateType::Txid(_rules) => true,
464-
BitcoinPredicateType::Inputs(_rules) => true,
465-
BitcoinPredicateType::Outputs(_rules) => false,
466-
BitcoinPredicateType::Protocol(Protocols::Ordinal(_)) => false,
467-
BitcoinPredicateType::Protocol(Protocols::Stacks(_)) => false,
468-
}
469-
}
470-
471-
pub fn include_outputs(&self) -> bool {
472-
match &self {
473-
BitcoinPredicateType::Block => true,
474-
BitcoinPredicateType::Txid(_rules) => true,
475-
BitcoinPredicateType::Inputs(_rules) => false,
476-
BitcoinPredicateType::Outputs(_rules) => true,
477-
BitcoinPredicateType::Protocol(Protocols::Ordinal(_)) => true,
478-
BitcoinPredicateType::Protocol(Protocols::Stacks(_)) => false,
479-
}
480-
}
481-
482-
pub fn include_witness(&self) -> bool {
483-
match &self {
484-
BitcoinPredicateType::Block => true,
485-
BitcoinPredicateType::Txid(_rules) => true,
486-
BitcoinPredicateType::Inputs(_rules) => false,
487-
BitcoinPredicateType::Outputs(_rules) => false,
488-
BitcoinPredicateType::Protocol(_rules) => false,
489-
}
490-
}
491-
}
492-
493475
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
494476
#[serde(rename_all = "snake_case")]
495477
pub enum InputPredicate {

components/chainhook-event-observer/src/observer/mod.rs

+30-27
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,11 @@ pub fn apply_bitcoin_block() {}
536536
pub fn rollback_bitcoin_block() {}
537537

538538
pub fn gather_proofs<'a>(
539-
chainhooks_to_trigger: &Vec<BitcoinTriggerChainhook<'a>>,
539+
trigger: &BitcoinTriggerChainhook<'a>,
540+
proofs: &mut HashMap<&'a TransactionIdentifier, String>,
540541
config: &EventObserverConfig,
541542
ctx: &Context,
542-
) -> HashMap<&'a TransactionIdentifier, String> {
543+
) {
543544
let bitcoin_client_rpc = Client::new(
544545
&config.bitcoind_rpc_url,
545546
Auth::UserPass(
@@ -549,35 +550,31 @@ pub fn gather_proofs<'a>(
549550
)
550551
.expect("unable to build http client");
551552

552-
let mut proofs = HashMap::new();
553-
for hook_to_trigger in chainhooks_to_trigger.iter() {
554-
for (transactions, block) in hook_to_trigger.apply.iter() {
555-
for transaction in transactions.iter() {
556-
if !proofs.contains_key(&transaction.transaction_identifier) {
557-
ctx.try_log(|logger| {
558-
slog::info!(
559-
logger,
560-
"Collecting proof for transaction {}",
561-
transaction.transaction_identifier.hash
562-
)
563-
});
564-
match get_bitcoin_proof(
565-
&bitcoin_client_rpc,
566-
&transaction.transaction_identifier,
567-
&block.block_identifier,
568-
) {
569-
Ok(proof) => {
570-
proofs.insert(&transaction.transaction_identifier, proof);
571-
}
572-
Err(e) => {
573-
ctx.try_log(|logger| slog::error!(logger, "{e}"));
574-
}
553+
for (transactions, block) in trigger.apply.iter() {
554+
for transaction in transactions.iter() {
555+
if !proofs.contains_key(&transaction.transaction_identifier) {
556+
ctx.try_log(|logger| {
557+
slog::info!(
558+
logger,
559+
"Collecting proof for transaction {}",
560+
transaction.transaction_identifier.hash
561+
)
562+
});
563+
match get_bitcoin_proof(
564+
&bitcoin_client_rpc,
565+
&transaction.transaction_identifier,
566+
&block.block_identifier,
567+
) {
568+
Ok(proof) => {
569+
proofs.insert(&transaction.transaction_identifier, proof);
570+
}
571+
Err(e) => {
572+
ctx.try_log(|logger| slog::error!(logger, "{e}"));
575573
}
576574
}
577575
}
578576
}
579577
}
580-
proofs
581578
}
582579

583580
pub async fn start_observer_commands_handler(
@@ -939,7 +936,13 @@ pub async fn start_observer_commands_handler(
939936
}
940937
}
941938

942-
let proofs = gather_proofs(&chainhooks_to_trigger, &config, &ctx);
939+
let mut proofs = HashMap::new();
940+
for trigger in chainhooks_to_trigger.iter() {
941+
if trigger.chainhook.include_proof {
942+
gather_proofs(&trigger, &mut proofs, &config, &ctx);
943+
}
944+
}
945+
943946
ctx.try_log(|logger| {
944947
slog::info!(
945948
logger,

components/chainhook-event-observer/src/observer/tests/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ fn bitcoin_chainhook_p2pkh(
100100
ExactMatchingRule::Equals(address.to_string()),
101101
)),
102102
action: HookAction::Noop,
103+
include_proof: None,
104+
include_inputs: None,
105+
include_outputs: None,
106+
include_witness: None,
103107
},
104108
);
105109

0 commit comments

Comments
 (0)