zakura-network 7.1.1

Networking code for the Zakura node. Internal crate, published to support cargo install zakura
use super::*;

#[test]
fn wrong_locator_ancestor_target_and_prepared_header_are_peer_attributable() {
    let (mut reactor, mut actions, snapshot, peer, _source, owner) = peer_violation_fixture();
    let active = reactor
        .peer_work_queue
        .active_mut(&peer)
        .expect("the fixture has active work");
    let wrong_ancestor = zakura_header_chain::Frontier::new(
        snapshot.frontiers.finalized.height,
        block::Hash([0x41; 32]),
    );
    let mut wrong_ancestor_header = *active.entries[0].header;
    wrong_ancestor_header.previous_block_hash = wrong_ancestor.hash;
    active.phase = HeaderTargetPhase::Receiving;
    active.common_ancestor = None;
    active.entries.clear();
    let wrong_ancestor_response = Headers {
        request_id: owner.request_id().get(),
        target_tip_hash: owner.header_authority().branch.target_tip_hash,
        common_ancestor_height: wrong_ancestor.height,
        common_ancestor_hash: wrong_ancestor.hash,
        complete: false,
        tree_aux_schema: AuxSchema::None,
        entries: vec![HeaderEntry {
            header: Arc::new(wrong_ancestor_header),
            body_size: 0,
            tree_aux: None,
        }],
    };
    assert!(
        reactor
            .codec
            .encode(&HeaderSyncMessage::Headers(wrong_ancestor_response.clone()))
            .is_ok(),
        "the wrong locator member is otherwise wire-valid"
    );
    reactor.handle_headers(
        peer.clone(),
        0,
        owner.header_authority(),
        wrong_ancestor_response,
    );
    assert_peer_violation(&mut actions, HeaderSyncMisbehavior::MalformedMessage);

    let (mut reactor, mut actions, snapshot, peer, _source, owner) = peer_violation_fixture();
    let active = reactor
        .peer_work_queue
        .active_mut(&peer)
        .expect("the fixture has active work");
    let header = active.entries[0].header.clone();
    let mut wrong_target_header = *header;
    wrong_target_header.time += chrono::Duration::seconds(1);
    active.phase = HeaderTargetPhase::Receiving;
    active.common_ancestor = None;
    active.entries.clear();
    let _ = active;
    reactor.peer_work_queue.set_capacity_for_test(&peer, 0, 1);
    reactor.handle_headers(
        peer.clone(),
        0,
        owner.header_authority(),
        Headers {
            request_id: owner.request_id().get(),
            target_tip_hash: owner.header_authority().branch.target_tip_hash,
            common_ancestor_height: snapshot.frontiers.finalized.height,
            common_ancestor_hash: snapshot.frontiers.finalized.hash,
            complete: true,
            tree_aux_schema: AuxSchema::None,
            entries: vec![HeaderEntry {
                header: Arc::new(wrong_target_header),
                body_size: 0,
                tree_aux: None,
            }],
        },
    );
    assert_peer_violation(&mut actions, HeaderSyncMisbehavior::MalformedMessage);

    let (mut reactor, mut actions, _snapshot, peer, source, owner) = peer_violation_fixture();
    reactor
        .peer_work_queue
        .active_mut(&peer)
        .expect("the fixture has active work")
        .phase = HeaderTargetPhase::Preparing;
    reactor.handle_header_target_prepared(
        peer,
        source,
        owner,
        HeaderTargetPreparationResult::Failed(invalid_header_failure(source, owner)),
    );
    assert_peer_violation(&mut actions, HeaderSyncMisbehavior::InvalidHeader);
}

#[test]
fn typed_taxonomy_scores_only_exact_attributed_header_peer_faults() {
    let (mut reactor, mut actions, _snapshot, peer, source, owner) = peer_violation_fixture();
    let subject = zakura_header_chain::ErrorSubject::Branch(owner.header_authority().branch);

    for (category, expected) in [
        (
            zakura_header_chain::ErrorCategory::MalformedProtocol,
            HeaderSyncMisbehavior::MalformedMessage,
        ),
        (
            zakura_header_chain::ErrorCategory::InvalidHeader,
            HeaderSyncMisbehavior::InvalidHeader,
        ),
    ] {
        let error = zakura_header_chain::HeaderChainError::new(
            category,
            subject,
            None,
            None,
            zakura_header_chain::Attribution::HeaderPeer(source),
            None,
        );
        reactor.handle_typed_failure(peer.clone(), source, &error);
        assert_peer_violation(&mut actions, expected);
    }

    for category in [
        zakura_header_chain::ErrorCategory::ValidLosingFork,
        zakura_header_chain::ErrorCategory::DeferredHeader,
        zakura_header_chain::ErrorCategory::BodyPayloadMismatch,
        zakura_header_chain::ErrorCategory::ConsensusBodyInvalid,
        zakura_header_chain::ErrorCategory::OperatorIneligible,
        zakura_header_chain::ErrorCategory::StaleTargetOrGeneration,
        zakura_header_chain::ErrorCategory::LocalAnchorOrIncoherence,
        zakura_header_chain::ErrorCategory::LocalResourceOrStorage,
    ] {
        let error = zakura_header_chain::HeaderChainError::new(
            category,
            subject,
            None,
            None,
            zakura_header_chain::Attribution::HeaderPeer(source),
            None,
        );
        reactor.handle_typed_failure(peer.clone(), source, &error);
        assert!(
            actions.try_recv().is_err(),
            "{category:?} cannot cross the header-peer scoring boundary"
        );
    }

    let wrong_source = zakura_header_chain::SourceId::from_digest([0x72; 32]);
    for category in [
        zakura_header_chain::ErrorCategory::MalformedProtocol,
        zakura_header_chain::ErrorCategory::InvalidHeader,
    ] {
        for attribution in [
            zakura_header_chain::Attribution::None,
            zakura_header_chain::Attribution::HeaderPeer(wrong_source),
            zakura_header_chain::Attribution::BodyPeer(source),
            zakura_header_chain::Attribution::AuxPeer(source),
        ] {
            let error = zakura_header_chain::HeaderChainError::new(
                category,
                subject,
                None,
                None,
                attribution,
                None,
            );
            reactor.handle_typed_failure(peer.clone(), source, &error);
            assert!(
                actions.try_recv().is_err(),
                "{category:?} with {attribution:?} cannot score this header peer"
            );
        }
    }
}

#[test]
fn response_completion_requires_the_reserved_branch_scope() {
    let (mut reactor, mut actions, snapshot, peer, _source, owner) = peer_violation_fixture();
    let expected = reactor
        .peer_work_queue
        .active(&peer)
        .expect("the fixture has active work")
        .clone();
    let mut wrong_scope = owner.header_authority();
    wrong_scope.header_generation = wrong_scope
        .header_generation
        .checked_next()
        .expect("the fixture generation has a successor");
    reactor.handle_headers(
        peer.clone(),
        0,
        wrong_scope,
        Headers {
            request_id: owner.request_id().get(),
            target_tip_hash: owner.header_authority().branch.target_tip_hash,
            common_ancestor_height: snapshot.frontiers.finalized.height,
            common_ancestor_hash: snapshot.frontiers.finalized.hash,
            complete: true,
            tree_aux_schema: AuxSchema::None,
            entries: Vec::new(),
        },
    );
    assert_eq!(reactor.peer_work_queue.active(&peer), Some(&expected));
    assert!(
        actions.try_recv().is_err(),
        "a scope-mismatched page has no peer or scheduling effect"
    );

    let (mut reactor, mut actions, _snapshot, peer, _source, owner) = peer_violation_fixture();
    let expected = reactor
        .peer_work_queue
        .active(&peer)
        .expect("the fixture has active work")
        .clone();
    let mut wrong_scope = owner.header_authority();
    wrong_scope.branch = zakura_header_chain::BranchId::new(
        owner.header_authority().branch.anchor_hash,
        block::Hash([0x73; 32]),
    );
    reactor.handle_headers_outcome(
        peer.clone(),
        0,
        wrong_scope,
        HeadersOutcome {
            request_id: owner.request_id().get(),
            target_tip_hash: owner.header_authority().branch.target_tip_hash,
            outcome: HeadersOutcomeCode::Busy,
        },
    );
    assert_eq!(reactor.peer_work_queue.active(&peer), Some(&expected));
    assert!(
        actions.try_recv().is_err(),
        "a scope-mismatched outcome has no peer or scheduling effect"
    );
}

#[test]
fn response_exceeding_its_owned_reservation_is_malformed_and_releases_capacity() {
    let (mut reactor, mut actions, _snapshot, peer, _source, owner) = peer_violation_fixture();
    let active = reactor
        .peer_work_queue
        .active_mut(&peer)
        .expect("the fixture has active work");
    let entry = active.entries[0].clone();
    active.phase = HeaderTargetPhase::Receiving;
    active.entries.clear();
    let returned_ancestor = active.common_ancestor.expect("the fixture has an ancestor");
    let _ = active;
    reactor.peer_work_queue.set_capacity_for_test(&peer, 0, 1);
    let response = Headers {
        request_id: owner.request_id().get(),
        target_tip_hash: owner.header_authority().branch.target_tip_hash,
        common_ancestor_height: returned_ancestor.height,
        common_ancestor_hash: returned_ancestor.hash,
        complete: false,
        tree_aux_schema: AuxSchema::None,
        entries: vec![entry; 2],
    };
    reactor.handle_headers(peer.clone(), 0, owner.header_authority(), response);

    assert!(
        reactor.peer_work_queue.active(&peer).is_none(),
        "over-reservation retires the target and releases its reservation"
    );
    assert_peer_violation(&mut actions, HeaderSyncMisbehavior::MalformedMessage);
    assert_eq!(
        reactor.peer_work_queue.unowned_chunk_capacity(),
        crate::zakura::header_sync::scheduler::peer_work::HEADER_CHUNK_BUDGET_CAPACITY_V1,
    );
}