1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Prints Zebra checkpoints as "height hash" output lines.
//!
//! Get all the blocks up to network current tip and print the ones that are
//! checkpoints according to rules.
//!
//! For usage please refer to the program help: `zebra-checkpoints --help`
//!
//! zebra-consensus accepts an ordered list of checkpoints, starting with the
//! genesis block. Checkpoint heights can be chosen arbitrarily.

#![deny(missing_docs)]
#![allow(clippy::try_err)]

use color_eyre::eyre::{ensure, Result};
use serde_json::Value;
use std::process::Stdio;
use structopt::StructOpt;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

use zebra_chain::block;

#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;

mod args;

/// We limit the memory usage for each checkpoint, based on the cumulative size of
/// the serialized blocks in the chain. Deserialized blocks are slightly larger
/// than serialized blocks, but they should be within a constant factor of the
/// serialized size.
const MAX_CHECKPOINT_BYTE_COUNT: u64 = 32 * 1024 * 1024;

/// Initialise tracing using its defaults.
fn init_tracing() {
    tracing_subscriber::Registry::default()
        .with(tracing_error::ErrorLayer::default())
        .init();
}

/// Return a new `zcash-cli` command, including the `zebra-checkpoints`
/// passthrough arguments.
fn passthrough_cmd() -> std::process::Command {
    let args = args::Args::from_args();
    let mut cmd = std::process::Command::new(&args.cli);

    if !args.zcli_args.is_empty() {
        cmd.args(&args.zcli_args);
    }
    cmd
}

/// Run `cmd` and return its output as a string.
fn cmd_output(cmd: &mut std::process::Command) -> Result<String> {
    // Capture stdout, but send stderr to the user
    let output = cmd.stderr(Stdio::inherit()).output()?;

    // Make sure the command was successful
    #[cfg(unix)]
    ensure!(
        output.status.success(),
        "Process failed: exit status {:?}, signal: {:?}",
        output.status.code(),
        output.status.signal()
    );
    #[cfg(not(unix))]
    ensure!(
        output.status.success(),
        "Process failed: exit status {:?}",
        output.status.code()
    );

    // Make sure the output is valid UTF-8
    let s = String::from_utf8(output.stdout)?;
    Ok(s)
}

fn main() -> Result<()> {
    init_tracing();

    color_eyre::install()?;

    // get the current block count
    let mut cmd = passthrough_cmd();
    cmd.arg("getblockcount");
    // calculate the maximum height
    let height_limit: block::Height = cmd_output(&mut cmd)?.trim().parse()?;
    assert!(height_limit <= block::Height::MAX);
    // Checkpoints must be on the main chain, so we skip blocks that are within the
    // Zcash reorg limit.
    let height_limit = height_limit
        .0
        .checked_sub(zebra_state::MAX_BLOCK_REORG_HEIGHT)
        .map(block::Height)
        .expect("zcashd has some mature blocks: wait for zcashd to sync more blocks");

    let starting_height = args::Args::from_args().last_checkpoint.map(block::Height);
    if starting_height.is_some() {
        // Since we're about to add 1, height needs to be strictly less than the maximum
        assert!(starting_height.unwrap() < block::Height::MAX);
    }
    // Start at the next block after the last checkpoint.
    // If there is no last checkpoint, start at genesis (height 0).
    let starting_height = starting_height.map_or(0, |block::Height(h)| h + 1);

    assert!(
        starting_height < height_limit.0,
        "No mature blocks after the last checkpoint: wait for zcashd to sync more blocks"
    );

    // set up counters
    let mut cumulative_bytes: u64 = 0;
    let mut height_gap: block::Height = block::Height(0);

    // loop through all blocks
    for x in starting_height..height_limit.0 {
        // unfortunately we need to create a process for each block
        let mut cmd = passthrough_cmd();

        // get block data
        cmd.args(&["getblock", &x.to_string()]);
        let output = cmd_output(&mut cmd)?;
        // parse json
        let v: Value = serde_json::from_str(&output)?;

        // get the values we are interested in
        let hash: block::Hash = v["hash"].as_str().unwrap().parse()?;
        let height = block::Height(v["height"].as_u64().unwrap() as u32);
        assert!(height <= block::Height::MAX);
        assert_eq!(x, height.0);
        let size = v["size"].as_u64().unwrap();

        // compute
        cumulative_bytes += size;
        height_gap = block::Height(height_gap.0 + 1);

        // check if checkpoint
        if height == block::Height(0)
            || cumulative_bytes >= MAX_CHECKPOINT_BYTE_COUNT
            || height_gap.0 >= zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP as u32
        {
            // print to output
            println!("{} {}", height.0, hash);

            // reset counters
            cumulative_bytes = 0;
            height_gap = block::Height(0);
        }
    }

    Ok(())
}