bitcoind_client/
txoo_follower.rsuse crate::follower::{Error, FollowAction, SourceFollower, Tracker};
use crate::BlockSource;
use bitcoin::hash_types::FilterHeader;
use bitcoin::{Block, BlockHash};
use bitcoin::blockdata::block::Header as BlockHeader;
use txoo::proof::TxoProof;
use txoo::source::Error as TxooSourceError;
use txoo::source::Source;
pub enum FollowWithProofAction {
None,
BlockAdded(Block, TxoProof),
BlockReorged(Block, TxoProof, BlockHeader, FilterHeader),
}
pub struct SourceWithTxooProofFollower {
source_follower: SourceFollower,
txoo_source: Box<dyn Source>,
}
impl SourceWithTxooProofFollower {
pub fn new(block_source: Box<dyn BlockSource>, source: Box<dyn Source>) -> Self {
SourceWithTxooProofFollower {
source_follower: SourceFollower::new(block_source),
txoo_source: source,
}
}
pub async fn follow_with_proof(
&self,
current_height: u32,
current_hash: BlockHash,
tracker: &impl Tracker,
) -> Result<FollowWithProofAction, Error> {
let action = self
.source_follower
.follow(current_height, current_hash)
.await?;
match action {
FollowAction::None => Ok(FollowWithProofAction::None),
FollowAction::BlockAdded(block) => {
self.txoo_source
.on_new_block(current_height + 1, &block)
.await;
let (attestation, prev_filter_header) =
match self.txoo_source.get(current_height + 1, &block).await {
Ok(result) => result,
Err(TxooSourceError::NotExists) => {
return Ok(FollowWithProofAction::None);
}
Err(e) => return Err(Error::SourceError(format!("{:?}", e))),
};
let pubkey = self.txoo_source.oracle_setup().await.public_key;
let attestations = vec![(pubkey, attestation)];
let (txids, outpoints) = tracker.forward_watches().await;
let proof = TxoProof::prove(
attestations,
&prev_filter_header,
&block,
current_height + 1,
&outpoints,
&txids,
);
Ok(FollowWithProofAction::BlockAdded(block, proof))
}
FollowAction::BlockReorged(block, prev_block_header) => {
assert!(current_height > 0);
let (attestation, prev_filter_header) = self
.txoo_source
.get(current_height, &block)
.await
.map_err(|e| {
Error::SourceError(format!("error getting current attestation: {:?}", e))
})?;
let pubkey = self.txoo_source.oracle_setup().await.public_key;
let attestations = vec![(pubkey, attestation)];
let (txids, outpoints) = tracker.reverse_watches().await;
let proof = TxoProof::prove(
attestations,
&prev_filter_header,
&block,
current_height,
&outpoints,
&txids,
);
Ok(FollowWithProofAction::BlockReorged(
block,
proof,
prev_block_header,
prev_filter_header,
))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dummy::DummyTxooSource;
use crate::test_utils::{DummyBlockSource, DummyTracker};
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::hash_types::FilterHeader;
#[tokio::test]
async fn follow_with_proof_test() {
let secp = Secp256k1::new();
let tracker = DummyTracker();
let mut source = DummyBlockSource::new();
let txoo_source = Box::new(DummyTxooSource::new());
let follower =
SourceWithTxooProofFollower::new(Box::new(source.clone()), txoo_source.clone());
let genesis_hash = source.genesis_hash();
txoo_source
.on_new_block(0, &source.get_block_sync(&source.genesis_hash()))
.await;
if let FollowWithProofAction::None = follower
.follow_with_proof(0, genesis_hash, &tracker)
.await
.unwrap()
{
} else {
panic!("expected None");
}
let header1 = source.add();
let hash1 = match follower
.follow_with_proof(0, genesis_hash, &tracker)
.await
.unwrap()
{
FollowWithProofAction::BlockAdded(block, proof) => {
proof
.verify(1, &header1, None, &FilterHeader::all_zeros(), &[], &secp)
.expect("verify");
block.block_hash()
}
_ => panic!("expected block added with proof"),
};
if let FollowWithProofAction::None = follower
.follow_with_proof(1, hash1, &tracker)
.await
.unwrap()
{
} else {
panic!("expected None");
}
source.remove();
let action = follower
.follow_with_proof(1, hash1, &tracker)
.await
.unwrap();
if let FollowWithProofAction::BlockReorged(
block,
_proof,
prev_block_header,
prev_filter_header,
) = action
{
assert_eq!(block.block_hash(), hash1);
assert_eq!(prev_filter_header, FilterHeader::all_zeros());
assert_eq!(
prev_block_header,
source.get_header(&genesis_hash).await.unwrap().header
);
} else {
panic!("expected reorg");
}
}
}