feat: ability to control inclusion of inputs/outputs/proofs/witness

This commit is contained in:
Ludo Galabru
2023-04-12 21:51:39 -04:00
parent 1a433e5804
commit daf55476c9
9 changed files with 113 additions and 91 deletions

View File

@@ -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:

View File

@@ -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,
},
);

View File

@@ -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);
}

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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<_>>(),
@@ -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<_>>(),
@@ -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| {
if predicate_spec.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),
"txin": format!("0x{}", input.previous_output.txid),
"vout": input.previous_output.vout,
"sequence": input.sequence,
})
}).collect::<Vec<_>>()));
})
.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>(

View File

@@ -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)]
@@ -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),
})
}
}
@@ -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")]
@@ -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 {

View File

@@ -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(
@@ -549,9 +550,7 @@ 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 (transactions, block) in trigger.apply.iter() {
for transaction in transactions.iter() {
if !proofs.contains_key(&transaction.transaction_identifier) {
ctx.try_log(|logger| {
@@ -576,8 +575,6 @@ pub fn gather_proofs<'a>(
}
}
}
}
proofs
}
pub async fn start_observer_commands_handler(
@@ -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,

View File

@@ -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,
},
);