Skip to content

Commit 50dd26f

Browse files
committed
fix: correctly determine PoX vs PoB block commitments (#499)
### Description Fixes #496 ### Checklist - [X] All tests pass - [ ] Tests added in this PR (if applicable)
1 parent d67d1e4 commit 50dd26f

File tree

2 files changed

+61
-46
lines changed

2 files changed

+61
-46
lines changed

components/chainhook-sdk/src/chainhooks/types.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -573,10 +573,6 @@ pub struct PoxConfig {
573573
}
574574

575575
impl PoxConfig {
576-
pub fn is_consensus_rewarding_participants_at_block_height(&self, block_height: u64) -> bool {
577-
self.get_pos_in_pox_cycle(block_height) < self.reward_phase_len
578-
}
579-
580576
pub fn get_pox_cycle_len(&self) -> u64 {
581577
self.prepare_phase_len + self.reward_phase_len
582578
}

components/chainhook-sdk/src/indexer/bitcoin/mod.rs

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -509,64 +509,83 @@ fn try_parse_stacks_operation(
509509
let mut pox_sats_burnt = 0;
510510
let mut pox_sats_transferred = vec![];
511511

512-
// We need to determine wether the transaction was a PoB or a Pox commitment
513-
let mining_output_index = if pox_config
514-
.is_consensus_rewarding_participants_at_block_height(block_height)
512+
let output_1 = outputs
513+
.get(1)
514+
.ok_or(format!("expected output 1 not found"))
515+
.ok()?;
516+
let script_1 = output_1
517+
.script_pub_key
518+
.script()
519+
.map_err(|_e| format!("expected output 1 corrupted"))
520+
.ok()?;
521+
let address_1 = Address::from_script(&script_1, bitcoin::Network::Bitcoin)
522+
.map_err(|_e| format!("expected output 1 corrupted"))
523+
.ok()?;
524+
525+
let output_2 = outputs
526+
.get(2)
527+
.ok_or(format!("expected output 2 not found"))
528+
.ok()?;
529+
let script_2 = output_2
530+
.script_pub_key
531+
.script()
532+
.map_err(|_e| format!("expected output 2 corrupted"))
533+
.ok()?;
534+
let address_2 = Address::from_script(&script_2, bitcoin::Network::Bitcoin)
535+
.map_err(|_e| format!("expected output 2 corrupted"))
536+
.ok()?;
537+
538+
let output_1_is_burn = address_1.to_string().eq(pox_config.get_burn_address());
539+
let output_2_is_burn = address_2.to_string().eq(pox_config.get_burn_address());
540+
541+
// PoX commitments have the following outputs:
542+
// - 0: OP_RETURN
543+
// - 1: rewarding address (could be a reward address, could be burn address in some rare cases)
544+
// - 2: rewarding address (could be a reward address, could be burn address in some rare cases; always burn address if 1 was burn address)
545+
// - [3-n]: change outputs
546+
//
547+
// PoB commitments have:
548+
// - 0: OP_RETURN
549+
// - 1: Burn address
550+
// - [3-n]: change outputs
551+
//
552+
// So, to determine if PoX vs PoB, we check if output 1 is the burn address
553+
// - If not, we definitely have a PoX block commitment
554+
// - If it is, we need to check if output 2 is the burn address
555+
// - If it is, we have a PoX block commitment
556+
// - If not, we have a PoB block commitment
557+
//
558+
// The only assumption we're making in this logic is that the first change output doesn't
559+
// get sent to the burn address, in which case we'd incorrectly label a PoB block commitment as Pox.
560+
let mining_output_index = if !output_1_is_burn || (output_1_is_burn && output_2_is_burn)
515561
{
562+
// We have a PoX Block Commitment
516563
// Output 0 is OP_RETURN
517564
// Output 1 is rewarding Address 1
518-
let pox_output_1 = outputs
519-
.get(1)
520-
.ok_or(format!("expected pox output 1 not found"))
521-
.ok()?;
522-
let pox_script_1 = pox_output_1
523-
.script_pub_key
524-
.script()
525-
.map_err(|_e| format!("expected pox output 1 corrupted"))
526-
.ok()?;
527-
let pox_address_1 = Address::from_script(&pox_script_1, bitcoin::Network::Bitcoin)
528-
.map_err(|_e| format!("expected pox output 1 corrupted"))
529-
.ok()?;
530-
if pox_address_1.to_string().eq(&pox_config.get_burn_address()) {
531-
pox_sats_burnt += pox_output_1.value.to_sat();
565+
if output_1_is_burn {
566+
pox_sats_burnt += output_1.value.to_sat();
532567
} else {
533568
pox_sats_transferred.push(PoxReward {
534-
recipient_address: pox_address_1.to_string(),
535-
amount: pox_output_1.value.to_sat(),
569+
recipient_address: address_1.to_string(),
570+
amount: output_1.value.to_sat(),
536571
});
537572
}
538573
// Output 2 is rewarding Address 2
539-
let pox_output_2 = outputs
540-
.get(2)
541-
.ok_or(format!("expected pox output 2 not found"))
542-
.ok()?;
543-
let pox_script_2 = pox_output_2
544-
.script_pub_key
545-
.script()
546-
.map_err(|_e| format!("expected pox output 2 corrupted"))
547-
.ok()?;
548-
let pox_address_2 = Address::from_script(&pox_script_2, bitcoin::Network::Bitcoin)
549-
.map_err(|_e| format!("expected pox output 2 corrupted"))
550-
.ok()?;
551-
if pox_address_2.to_string().eq(&pox_config.get_burn_address()) {
552-
pox_sats_burnt += pox_output_2.value.to_sat();
574+
if output_2_is_burn {
575+
pox_sats_burnt += output_2.value.to_sat();
553576
} else {
554577
pox_sats_transferred.push(PoxReward {
555-
recipient_address: pox_address_2.to_string(),
556-
amount: pox_output_2.value.to_sat(),
578+
recipient_address: address_2.to_string(),
579+
amount: output_2.value.to_sat(),
557580
});
558581
}
559582
// Output 3 is used for miner chained commitments
560583
3
561584
} else {
585+
// We have a PoB Block Commitment
562586
// Output 0 is OP_RETURN
563-
// Output 1 should be a Burn Address
564-
let burn_output = outputs
565-
.get(1)
566-
.ok_or(format!("expected burn address not found"))
567-
.ok()?;
568-
// Todo: Ensure that we're looking at a burn address
569-
pox_sats_burnt += burn_output.value.to_sat();
587+
// Output 1 is be a Burn Address
588+
pox_sats_burnt += output_1.value.to_sat();
570589
// Output 2 is used for miner chained commitments
571590
2
572591
};

0 commit comments

Comments
 (0)