mirror of
https://github.com/alexgo-io/bitcoin-indexer.git
synced 2026-01-12 16:52:57 +08:00
feat: ability to control inclusion of inputs/outputs/proofs/witness
This commit is contained in:
12
README.md
12
README.md
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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| {
|
||||
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>(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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(
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user