zakura-state 7.0.0

State contextual verification and storage code for the Zakura node. Internal crate, published to support cargo install zakura
Documentation
use super::*;

struct TestStalledMaintenance {
    stalled_version: Mutex<Option<StateVersion>>,
    clear_on_reevaluation: bool,
    reevaluations: AtomicUsize,
}

impl HeaderChainMaintenance for TestStalledMaintenance {
    fn resource_stalled_version(&self) -> Option<StateVersion> {
        *self
            .stalled_version
            .lock()
            .expect("the stalled-version test lock is available")
    }

    fn earliest_deferred(
        &self,
    ) -> Result<Option<chrono::DateTime<chrono::Utc>>, HeaderChainStoreError> {
        Ok(None)
    }

    fn now(&self) -> chrono::DateTime<chrono::Utc> {
        chrono::Utc::now()
    }

    fn reevaluate_deferred(&self) -> Result<(), HeaderChainStoreError> {
        self.reevaluations.fetch_add(1, Ordering::SeqCst);
        if self.clear_on_reevaluation {
            *self
                .stalled_version
                .lock()
                .map_err(|_| HeaderChainStoreError::WriterPoisoned)? = None;
        }
        Ok(())
    }
}

#[test]
fn deferred_maintenance_wakes_an_idle_writer_at_the_deadline() {
    let (sender, mut receiver) = mpsc::unbounded_channel();
    let now = chrono::Utc::now();
    let maintenance = TestDeferredMaintenance {
        deadlines: Mutex::new(VecDeque::from([now + chrono::Duration::milliseconds(20)])),
        sender: Mutex::new(Some(sender)),
        reevaluations: AtomicUsize::new(0),
    };
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_time()
        .build()
        .expect("the test deadline runtime is available");
    let started = Instant::now();
    let message = receive_until_deferred_deadline(&mut receiver, Some(&maintenance), &runtime)
        .expect("deadline maintenance succeeds");

    assert!(message.is_none());
    assert_eq!(maintenance.reevaluations.load(Ordering::SeqCst), 1);
    assert!(started.elapsed() >= Duration::from_millis(20));
}

#[test]
fn deferred_maintenance_advances_to_the_next_deadline() {
    let (sender, mut receiver) = mpsc::unbounded_channel();
    let now = chrono::Utc::now();
    let maintenance = TestDeferredMaintenance {
        deadlines: Mutex::new(VecDeque::from([
            now + chrono::Duration::milliseconds(10),
            now + chrono::Duration::milliseconds(30),
        ])),
        sender: Mutex::new(Some(sender)),
        reevaluations: AtomicUsize::new(0),
    };
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_time()
        .build()
        .expect("the test deadline runtime is available");
    let message = receive_until_deferred_deadline(&mut receiver, Some(&maintenance), &runtime)
        .expect("both deadline maintenance passes succeed");

    assert!(message.is_none());
    assert_eq!(maintenance.reevaluations.load(Ordering::SeqCst), 2);
}

#[test]
fn state_messages_preempt_the_deferred_deadline() {
    let (sender, mut receiver) = mpsc::unbounded_channel();
    let (rsp_tx, _rsp_rx) = oneshot::channel();
    let delayed_sender = sender.clone();
    let delayed_send = std::thread::spawn(move || {
        std::thread::sleep(Duration::from_millis(20));
        delayed_sender
            .send(NonFinalizedWriteMessage::Invalidate {
                hash: block::Hash([7; 32]),
                rsp_tx,
            })
            .expect("the delayed state message is queued");
    });
    let now = chrono::Utc::now();
    let maintenance = TestDeferredMaintenance {
        deadlines: Mutex::new(VecDeque::from([now + chrono::Duration::hours(1)])),
        sender: Mutex::new(Some(sender)),
        reevaluations: AtomicUsize::new(0),
    };
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_time()
        .build()
        .expect("the test deadline runtime is available");
    let started = Instant::now();
    let message = receive_until_deferred_deadline(&mut receiver, Some(&maintenance), &runtime)
        .expect("the queued state message wins");
    delayed_send
        .join()
        .expect("the delayed test sender does not panic");

    assert!(matches!(
        message,
        Some(NonFinalizedWriteMessage::Invalidate { hash, .. })
            if hash == block::Hash([7; 32])
    ));
    assert_eq!(maintenance.reevaluations.load(Ordering::SeqCst), 0);
    assert!(started.elapsed() < Duration::from_secs(1));
}

#[test]
fn writer_reevaluates_a_resource_stall_without_a_deferred_deadline() {
    let maintenance = TestStalledMaintenance {
        stalled_version: Mutex::new(Some(StateVersion::new(7))),
        clear_on_reevaluation: true,
        reevaluations: AtomicUsize::new(0),
    };
    let mut last_resource_stall_recovery = None;

    recover_resource_stall(Some(&maintenance), &mut last_resource_stall_recovery)
        .expect("resource-stall maintenance succeeds");

    assert_eq!(maintenance.reevaluations.load(Ordering::SeqCst), 1);
    assert_eq!(last_resource_stall_recovery, None);
}

#[test]
fn writer_reevaluates_a_resource_stall_once_per_state_version() {
    let maintenance = TestStalledMaintenance {
        stalled_version: Mutex::new(Some(StateVersion::new(7))),
        clear_on_reevaluation: false,
        reevaluations: AtomicUsize::new(0),
    };
    let mut last_resource_stall_recovery = None;

    for (iteration, expected_reevaluations) in [(1u8, 1usize), (2, 1), (3, 2)] {
        if iteration == 3 {
            *maintenance
                .stalled_version
                .lock()
                .expect("the stalled-version test lock is available") = Some(StateVersion::new(8));
        }
        recover_resource_stall(Some(&maintenance), &mut last_resource_stall_recovery)
            .expect("bounded resource-stall maintenance succeeds");

        assert_eq!(
            maintenance.reevaluations.load(Ordering::SeqCst),
            expected_reevaluations
        );
    }
}

#[test]
fn idle_writer_promotes_a_due_persisted_deferred_header() {
    #[derive(Copy, Clone)]
    struct FixedClock(chrono::DateTime<chrono::Utc>);

    impl zakura_header_chain::Clock for FixedClock {
        fn now(&self) -> chrono::DateTime<chrono::Utc> {
            self.0
        }
    }

    let _init_guard = zakura_test::init();
    let network = Network::new_regtest(Default::default());
    let finalized_state = FinalizedState::new(&Config::ephemeral(), &network)
        .expect("the deferred fixture finalized state opens");
    let anchor = regtest_genesis_block();
    let anchor_height = anchor
        .coinbase_height()
        .expect("the deferred fixture anchor has a height");
    let writer = header_writer(&finalized_state, &network, anchor_height, &anchor);
    let initial = writer.runtime.publisher().snapshot();
    let lease = writer
        .runtime
        .reader()
        .validation_context(anchor.hash())
        .expect("the anchor validation context read succeeds")
        .expect("the anchor validation context exists");
    let rules = HeaderRules::for_validation_lease(&lease)
        .expect("the deferred fixture validation rules are coherent");
    let preparation_clock = FixedClock(anchor.header.time);
    let mut future_header = *anchor.header;
    future_header.previous_block_hash = anchor.hash();
    future_header.time += chrono::Duration::hours(3);
    future_header.nonce.0[0] = 0xd0;
    let future_header = Arc::new(future_header);
    let batch = zakura_header_chain::prepare_headers(
        HeaderBatchInput::new(std::slice::from_ref(&future_header)),
        lease.parent(),
        &rules,
        &preparation_clock,
    )
    .expect("the locally future header is prepared as deferred");
    let deferred_until = future_header.time - chrono::Duration::hours(2);
    assert!(deferred_until < chrono::Utc::now());
    let future = Frontier::new(
        anchor_height
            .next()
            .expect("the genesis anchor has a next height"),
        future_header.hash(),
    );
    let owner = header_owner(&initial, future.hash, 41, 42);
    let insertion_context = TransitionContext {
        config: &writer.config,
        clock: &preparation_clock,
        full_state_authority: None,
        retention_references: &[],
    };
    writer
        .runtime
        .apply(
            TransitionRequest {
                expected_version: initial.state_version,
                event: TransitionEvent::InsertHeaders(Box::new(InsertHeaders {
                    owner,
                    source: SourceId::from_digest([0xd1; 32]),
                    parent_hash: anchor.hash(),
                    target_tip_hash: future.hash,
                    completion: TargetCompletion::TargetComplete {
                        common_ancestor: Frontier::new(anchor_height, anchor.hash()),
                    },
                    batch,
                    aux: Vec::new(),
                })),
            },
            &insertion_context,
        )
        .expect("the deferred header insertion commits");
    assert_eq!(
        writer
            .runtime
            .earliest_deferred()
            .expect("the deferred deadline is readable"),
        Some(deferred_until)
    );

    let (sender, mut receiver) = mpsc::unbounded_channel();
    let close_sender = std::thread::spawn(move || {
        std::thread::sleep(Duration::from_millis(20));
        drop(sender);
    });
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_time()
        .build()
        .expect("the test deadline runtime is available");
    let message = receive_until_deferred_deadline(&mut receiver, Some(&writer), &runtime)
        .expect("idle deferred maintenance succeeds");
    close_sender
        .join()
        .expect("the delayed channel closer does not panic");

    assert!(message.is_none());
    assert_eq!(
        writer.runtime.publisher().snapshot().frontiers.header_best,
        future
    );
    assert_eq!(
        writer
            .runtime
            .earliest_deferred()
            .expect("the promoted deadline index is readable"),
        None
    );
}