Skip to content

Commit

Permalink
fix: make bitcoin payload serialization deserializable (#569)
Browse files Browse the repository at this point in the history
When a Chainhook is triggered and we send a payload to a user, we filter
some values based on user settings (`include_proof`, `include_inputs`,
etc). This helps to reduce the payload size that is sent over the wire.

However, when we created this payload, we were omitting these fields
completely even though the corresponding type does not have those fields
as optional. This makes using our SDK types for deserialization
impossible.

This PR adds in those fields in all cases and sets them to empty values
if filtered out.
  • Loading branch information
MicaiahReid committed May 8, 2024
1 parent 6a8c086 commit 5f20a86
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 33 deletions.
81 changes: 52 additions & 29 deletions components/chainhook-sdk/src/chainhooks/bitcoin/mod.rs
Expand Up @@ -35,6 +35,7 @@ pub struct BitcoinTriggerChainhook<'a> {

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BitcoinTransactionPayload {
#[serde(flatten)]
pub block: BitcoinBlockData,
}

Expand Down Expand Up @@ -233,38 +234,60 @@ pub fn serialize_bitcoin_transactions_to_json<'a>(
.into_iter()
.map(|transaction| {
let mut metadata = serde_json::Map::new();
if predicate_spec.include_inputs {
metadata.insert(
"inputs".into(),
json!(transaction
.metadata
.inputs
.iter()
.map(|input| {
json!({

metadata.insert("fee".into(), json!(transaction.metadata.fee));
metadata.insert("index".into(), json!(transaction.metadata.index));

let inputs = if predicate_spec.include_inputs {
transaction
.metadata
.inputs
.iter()
.map(|input| {
let witness = if predicate_spec.include_witness {
input.witness.clone()
} else {
vec![]
};
json!({
"previous_output": {
"txin": input.previous_output.txid.hash.to_string(),
"vout": input.previous_output.vout,
"sequence": input.sequence,
})
"value": input.previous_output.value,
"block_height": input.previous_output.block_height,
},
"script_sig": input.script_sig,
"sequence": input.sequence,
"witness": witness
})
.collect::<Vec<_>>()),
);
}
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),
);
}
if !transaction.metadata.ordinal_operations.is_empty() {
metadata.insert(
"ordinal_operations".into(),
json!(transaction.metadata.ordinal_operations),
);
}
})
.collect::<Vec<_>>()
} else {
vec![]
};
metadata.insert("inputs".into(), json!(inputs));

let outputs = if predicate_spec.include_outputs {
transaction.metadata.outputs.clone()
} else {
vec![]
};
metadata.insert("outputs".into(), json!(outputs));

let stacks_ops = if transaction.metadata.stacks_operations.is_empty() {
vec![]
} else {
transaction.metadata.stacks_operations.clone()
};
metadata.insert("stacks_operations".into(), json!(stacks_ops));

let ordinals_ops = if transaction.metadata.ordinal_operations.is_empty() {
vec![]
} else {
transaction.metadata.ordinal_operations.clone()
};
metadata.insert("ordinal_operations".into(), json!(ordinals_ops));

metadata.insert(
"proof".into(),
json!(proofs.get(&transaction.transaction_identifier)),
Expand Down
59 changes: 59 additions & 0 deletions components/chainhook-sdk/src/chainhooks/bitcoin/tests.rs
@@ -1,8 +1,12 @@
use super::super::types::MatchingRule;
use super::*;
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;
use crate::types::BitcoinTransactionMetadata;
use chainhook_types::bitcoin::TxOut;

use chainhook_types::BitcoinNetwork;
use test_case::test_case;

#[test_case(
Expand Down Expand Up @@ -134,3 +138,58 @@ fn script_pubkey_evaluation(output: OutputPredicate, script_pubkey: &str, matche

assert_eq!(matches, predicate.evaluate_transaction_predicate(&tx, &ctx));
}

#[test_case(
true, true, true, true;
"including all optional fields"
)]
#[test_case(
false, false, false, false;
"omitting all optional fields"
)]

fn it_serdes_occurrence_payload(
include_proof: bool,
include_inputs: bool,
include_outputs: bool,
include_witness: bool,
) {
let transaction = generate_test_tx_bitcoin_p2pkh_transfer(
0,
&accounts::wallet_1_btc_address(),
&accounts::wallet_3_btc_address(),
3,
);
let block = generate_test_bitcoin_block(0, 0, vec![transaction.clone()], None);
let chainhook = &BitcoinChainhookSpecification {
uuid: "uuid".into(),
owner_uuid: None,
name: "name".into(),
network: BitcoinNetwork::Mainnet,
version: 0,
blocks: None,
start_block: None,
end_block: None,
expire_after_occurrence: None,
predicate: BitcoinPredicateType::Block,
action: HookAction::Noop,
include_proof,
include_inputs,
include_outputs,
include_witness,
enabled: true,
expired_at: None,
};
let trigger = BitcoinTriggerChainhook {
chainhook,
apply: vec![(vec![&transaction], &block)],
rollback: vec![],
};
let payload = serde_json::to_vec(&serialize_bitcoin_payload_to_json(
&trigger,
&HashMap::new(),
))
.unwrap();

let _: BitcoinChainhookOccurrencePayload = serde_json::from_slice(&payload[..]).unwrap();
}
6 changes: 2 additions & 4 deletions components/chainhook-sdk/src/observer/mod.rs
Expand Up @@ -522,21 +522,19 @@ pub fn start_event_observer(

pub async fn start_bitcoin_event_observer(
config: EventObserverConfig,
_observer_commands_tx: Sender<ObserverCommand>,
observer_commands_tx: Sender<ObserverCommand>,
observer_commands_rx: Receiver<ObserverCommand>,
observer_events_tx: Option<crossbeam_channel::Sender<ObserverEvent>>,
observer_sidecar: Option<ObserverSidecar>,
ctx: Context,
) -> Result<(), Box<dyn Error>> {
let chainhook_store = config.get_chainhook_store();

#[cfg(feature = "zeromq")]
{
let ctx_moved = ctx.clone();
let config_moved = config.clone();
let _ = hiro_system_kit::thread_named("ZMQ handler").spawn(move || {
let future =
zmq::start_zeromq_runloop(&config_moved, _observer_commands_tx, &ctx_moved);
let future = zmq::start_zeromq_runloop(&config_moved, observer_commands_tx, &ctx_moved);
let _ = hiro_system_kit::nestable_block_on(future);
});
}
Expand Down

0 comments on commit 5f20a86

Please sign in to comment.