blocks_iterator 0.2.0

Iterates Bitcoin blocks
Documentation
use crate::truncmap::TruncMap;
use crate::BlockExtra;
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use log::{debug, info, trace};
use std::sync::mpsc::Receiver;
use std::sync::mpsc::SyncSender;
use std::time::Instant;

pub struct Fee {
    receiver: Receiver<Option<BlockExtra>>,
    sender: SyncSender<Option<BlockExtra>>,
    utxo: Utxo,
}

struct Utxo {
    map: TruncMap,
    unspendable: u64,
}

impl Utxo {
    pub fn new() -> Self {
        Utxo {
            map: TruncMap::default(),
            unspendable: 0,
        }
    }

    pub fn add(&mut self, tx: &Transaction) -> Txid {
        let txid = tx.txid();
        for (i, output) in tx.output.iter().enumerate() {
            if output.script_pubkey.is_provably_unspendable() {
                self.unspendable += 1;
                continue;
            }
            self.map.insert(OutPoint::new(txid, i as u32), output);
        }
        txid
    }

    pub fn remove(&mut self, outpoint: OutPoint) -> TxOut {
        self.map.remove(&outpoint).unwrap()
    }
}

impl Fee {
    pub fn new(
        receiver: Receiver<Option<BlockExtra>>,
        sender: SyncSender<Option<BlockExtra>>,
    ) -> Fee {
        Fee {
            sender,
            receiver,
            utxo: Utxo::new(),
        }
    }

    pub fn start(&mut self) {
        info!("starting fee processer");
        let mut busy_time = 0u128;
        let mut total_txs = 0u64;
        loop {
            let received = self.receiver.recv().unwrap();
            let now = Instant::now();
            match received {
                Some(mut block_extra) => {
                    trace!("fee received: {}", block_extra.block_hash);
                    total_txs += block_extra.block.txdata.len() as u64;

                    if block_extra.height % 10_000 == 0 {
                        let (utxo_size, collision_size, utxo_capacity) = self.utxo.map.len();
                        info!(
                            "(utxo, collision, capacity): {:?} load:{:.1}% script on stack: {:.1}% unspendable:{}",
                            (utxo_size, collision_size, utxo_capacity),
                            (utxo_size as f64 / utxo_capacity as f64) * 100.0,
                            self.utxo.map.script_on_stack() * 100.0,
                            self.utxo.unspendable,
                        );
                    }
                    for tx in block_extra.block.txdata.iter() {
                        let txid = self.utxo.add(tx);
                        block_extra.tx_hashes.insert(txid);
                    }

                    for tx in block_extra.block.txdata.iter().skip(1) {
                        for input in tx.input.iter() {
                            let previous_txout = self.utxo.remove(input.previous_output);
                            block_extra
                                .outpoint_values
                                .insert(input.previous_output, previous_txout);
                        }
                    }
                    let coin_base_output_value = block_extra.block.txdata[0]
                        .output
                        .iter()
                        .map(|el| el.value)
                        .sum();
                    block_extra.outpoint_values.insert(
                        OutPoint::default(),
                        TxOut {
                            script_pubkey: Script::new(),
                            value: coin_base_output_value,
                        },
                    );

                    debug!(
                        "#{:>6} {} size:{:>7} txs:{:>4} total_txs:{:>9} fee:{:?}",
                        block_extra.height,
                        block_extra.block_hash,
                        block_extra.size,
                        block_extra.block.txdata.len(),
                        total_txs,
                        block_extra.fee(),
                    );

                    busy_time += now.elapsed().as_nanos();
                    self.sender.send(Some(block_extra)).unwrap();
                }
                None => break,
            }
        }

        self.sender.send(None).expect("fee: cannot send none");

        info!(
            "ending fee processer total tx {}, busy time: {}s",
            total_txs,
            busy_time / 1_000_000_000
        );
    }
}

#[cfg(test)]
mod test {
    use bitcoin::TxOut;

    #[test]
    fn test_size() {
        assert_eq!(
            std::mem::size_of::<Option<TxOut>>(),
            std::mem::size_of::<TxOut>()
        );
    }
}