<commit_before>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
    cluster::Cluster,
    experiments::{Context, Experiment, ExperimentParam},
    instance::Instance,
    tx_emitter::{execute_and_wait_transactions, EmitJobRequest},
};
use anyhow::ensure;
use async_trait::async_trait;
use diem_client::Client;
use diem_logger::prelude::*;
use diem_operational_tool::json_rpc::JsonRpcClientWrapper;
use diem_sdk::transaction_builder::TransactionFactory;
use diem_types::{
    account_address::AccountAddress,
    chain_id::ChainId,
    ledger_info::LedgerInfoWithSignatures,
    on_chain_config::{ConsensusConfigV1, OnChainConsensusConfig},
};
use std::{
    collections::HashSet,
    fmt,
    time::{Duration, Instant},
};
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
pub struct ReconfigurationParams {
    #[structopt(long, default_value = "101", help = "Number of epochs to trigger")]
    pub count: u64,
    #[structopt(long, help = "Emit p2p transfer transactions during experiment")]
    pub emit_txn: bool,
}

pub struct Reconfiguration {
    affected_peer_id: AccountAddress,
    affected_pod_name: String,
    count: u64,
    emit_txn: bool,
}

impl ExperimentParam for ReconfigurationParams {
    type E = Reconfiguration;
    fn build(self, cluster: &Cluster) -> Self::E {
        let full_node = cluster.random_fullnode_instance();
        let client = JsonRpcClientWrapper::new(full_node.json_rpc_url().into());
        let validator_info = client
            .validator_set(None)
            .expect("Unable to fetch validator set");
        let affected_peer_id = *validator_info[0].account_address();
        let validator_config = client
            .validator_config(affected_peer_id)
            .expect("Unable to fetch validator config");
        let affected_pod_name = std::str::from_utf8(&validator_config.human_name)
            .unwrap()
            .to_string();
        Self::E {
            affected_peer_id,
            affected_pod_name,
            count: self.count,
            emit_txn: self.emit_txn,
        }
    }
}

async fn expect_epoch(
    client: &Client,
    known_version: u64,
    expected_epoch: u64,
) -> anyhow::Result<u64> {
    let state_proof = client.get_state_proof(known_version).await?.into_inner();
    let li: LedgerInfoWithSignatures = bcs::from_bytes(&state_proof.ledger_info_with_signatures)?;
    let epoch = li.ledger_info().next_block_epoch();
    ensure!(
        epoch == expected_epoch,
        "Expect epoch {}, actual {}",
        expected_epoch,
        epoch
    );
    info!("Epoch {} is committed", epoch);
    Ok(li.ledger_info().version())
}

#[async_trait]
impl Experiment for Reconfiguration {
    fn affected_validators(&self) -> HashSet<String> {
        let mut nodes = HashSet::new();
        nodes.insert(self.affected_pod_name.clone());
        nodes
    }

    async fn run(&mut self, context: &mut Context<'_>) -> anyhow::Result<()> {
        let full_node = context.cluster.random_fullnode_instance();
        let tx_factory = TransactionFactory::new(ChainId::test());
        let mut full_node_client = full_node.json_rpc_client();
        let mut diem_root_account = context
            .tx_emitter
            .load_diem_root_account(&full_node_client)
            .await?;
        let allowed_nonce = 0;
        let emit_job = if self.emit_txn {
            info!("Start emitting txn");
            let instances: Vec<Instance> = context
                .cluster
                .validator_instances()
                .iter()
                .filter(|i| *i.peer_name() != self.affected_pod_name)
                .cloned()
                .collect();
            Some(
                context
                    .tx_emitter
                    .start_job(EmitJobRequest::for_instances(
                        instances,
                        context.global_emit_job_request,
                        0,
                        0,
                    ))
                    .await?,
            )
        } else {
            None
        };

        let timer = Instant::now();
        let mut version = expect_epoch(&full_node_client, 0, 1).await?;
        {
            info!("Remove and add back {}.", self.affected_pod_name);
            let validator_name = self.affected_pod_name.as_bytes().to_vec();
            let remove_txn = diem_root_account.sign_with_transaction_builder(
                tx_factory.remove_validator_and_reconfigure(
                    allowed_nonce,
                    validator_name.clone(),
                    self.affected_peer_id,
                ),
            );
            execute_and_wait_transactions(
                &mut full_node_client,
                &mut diem_root_account,
                vec![remove_txn],
            )
            .await?;
            version = expect_epoch(&full_node_client, version, 2).await?;
            let add_txn = diem_root_account.sign_with_transaction_builder(
                tx_factory.add_validator_and_reconfigure(
                    allowed_nonce,
                    validator_name.clone(),
                    self.affected_peer_id,
                ),
            );
            execute_and_wait_transactions(
                &mut full_node_client,
                &mut diem_root_account,
                vec![add_txn],
            )
            .await?;
            version = expect_epoch(&full_node_client, version, 3).await?;
        }

        {
            info!("Switch from 2-chain and 3-chain repetitively.");
            let two_chain_config =
                OnChainConsensusConfig::V1(ConsensusConfigV1 { two_chain: true });
            let three_chain_config = OnChainConsensusConfig::default();
            for i in 1..self.count / 2 {
                let two_chain_txn = diem_root_account.sign_with_transaction_builder(
                    tx_factory.update_diem_consensus_config(
                        allowed_nonce,
                        bcs::to_bytes(&two_chain_config).unwrap(),
                    ),
                );
                execute_and_wait_transactions(
                    &mut full_node_client,
                    &mut diem_root_account,
                    vec![two_chain_txn],
                )
                .await?;
                version = expect_epoch(&full_node_client, version, (i + 1) * 2).await?;
                let three_chain_txn = diem_root_account.sign_with_transaction_builder(
                    tx_factory.update_diem_consensus_config(
                        allowed_nonce,
                        bcs::to_bytes(&three_chain_config).unwrap(),
                    ),
                );
                execute_and_wait_transactions(
                    &mut full_node_client,
                    &mut diem_root_account,
                    vec![three_chain_txn],
                )
                .await?;
                version = expect_epoch(&full_node_client, version, (i + 1) * 2 + 1).await?;
            }
        }

        if self.count % 2 == 1 {
            let magic_number = 42;
            info!("Bump DiemVersion to {}", magic_number);
            let update_txn = diem_root_account.sign_with_transaction_builder(
                TransactionFactory::new(ChainId::test())
                    .update_diem_version(allowed_nonce, magic_number),
            );
            execute_and_wait_transactions(
                &mut full_node_client,
                &mut diem_root_account,
                vec![update_txn],
            )
            .await?;
            expect_epoch(&full_node_client, version, self.count + 1).await?;
        }
        let elapsed = timer.elapsed();
        if let Some(job) = emit_job {
            let stats = context.tx_emitter.stop_job(job).await;
            context
                .report
                .report_txn_stats(self.to_string(), stats, elapsed, "");
        } else {
            context.report.report_text(format!(
                "{} finished in {} seconds",
                self.to_string(),
                elapsed.as_secs()
            ));
        }

        Ok(())
    }

    fn deadline(&self) -> Duration {
        // allow each epoch to take 20 secs
        Duration::from_secs(self.count as u64 * 10)
    }
}

impl fmt::Display for Reconfiguration {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Reconfiguration: total epoch: {}", self.count)
    }
}
<commit_msg>[decoupled-execution] add cluster test<commit_after>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
    cluster::Cluster,
    experiments::{Context, Experiment, ExperimentParam},
    instance::Instance,
    tx_emitter::{execute_and_wait_transactions, EmitJobRequest},
};
use anyhow::ensure;
use async_trait::async_trait;
use diem_client::Client;
use diem_logger::prelude::*;
use diem_operational_tool::json_rpc::JsonRpcClientWrapper;
use diem_sdk::transaction_builder::TransactionFactory;
use diem_types::{
    account_address::AccountAddress,
    chain_id::ChainId,
    ledger_info::LedgerInfoWithSignatures,
    on_chain_config::{ConsensusConfigV2, OnChainConsensusConfig},
};
use std::{
    collections::HashSet,
    fmt,
    time::{Duration, Instant},
};
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
pub struct ReconfigurationParams {
    #[structopt(long, default_value = "101", help = "Number of epochs to trigger")]
    pub count: u64,
    #[structopt(long, help = "Emit p2p transfer transactions during experiment")]
    pub emit_txn: bool,
}

pub struct Reconfiguration {
    affected_peer_id: AccountAddress,
    affected_pod_name: String,
    count: u64,
    emit_txn: bool,
}

impl ExperimentParam for ReconfigurationParams {
    type E = Reconfiguration;
    fn build(self, cluster: &Cluster) -> Self::E {
        let full_node = cluster.random_fullnode_instance();
        let client = JsonRpcClientWrapper::new(full_node.json_rpc_url().into());
        let validator_info = client
            .validator_set(None)
            .expect("Unable to fetch validator set");
        let affected_peer_id = *validator_info[0].account_address();
        let validator_config = client
            .validator_config(affected_peer_id)
            .expect("Unable to fetch validator config");
        let affected_pod_name = std::str::from_utf8(&validator_config.human_name)
            .unwrap()
            .to_string();
        Self::E {
            affected_peer_id,
            affected_pod_name,
            count: self.count,
            emit_txn: self.emit_txn,
        }
    }
}

async fn expect_epoch(
    client: &Client,
    known_version: u64,
    expected_epoch: u64,
) -> anyhow::Result<u64> {
    let state_proof = client.get_state_proof(known_version).await?.into_inner();
    let li: LedgerInfoWithSignatures = bcs::from_bytes(&state_proof.ledger_info_with_signatures)?;
    let epoch = li.ledger_info().next_block_epoch();
    ensure!(
        epoch == expected_epoch,
        "Expect epoch {}, actual {}",
        expected_epoch,
        epoch
    );
    info!("Epoch {} is committed", epoch);
    Ok(li.ledger_info().version())
}

#[async_trait]
impl Experiment for Reconfiguration {
    fn affected_validators(&self) -> HashSet<String> {
        let mut nodes = HashSet::new();
        nodes.insert(self.affected_pod_name.clone());
        nodes
    }

    async fn run(&mut self, context: &mut Context<'_>) -> anyhow::Result<()> {
        let full_node = context.cluster.random_fullnode_instance();
        let tx_factory = TransactionFactory::new(ChainId::test());
        let mut full_node_client = full_node.json_rpc_client();
        let mut diem_root_account = context
            .tx_emitter
            .load_diem_root_account(&full_node_client)
            .await?;
        let allowed_nonce = 0;
        let emit_job = if self.emit_txn {
            info!("Start emitting txn");
            let instances: Vec<Instance> = context
                .cluster
                .validator_instances()
                .iter()
                .filter(|i| *i.peer_name() != self.affected_pod_name)
                .cloned()
                .collect();
            Some(
                context
                    .tx_emitter
                    .start_job(EmitJobRequest::for_instances(
                        instances,
                        context.global_emit_job_request,
                        0,
                        0,
                    ))
                    .await?,
            )
        } else {
            None
        };

        let timer = Instant::now();
        let mut version = expect_epoch(&full_node_client, 0, 1).await?;
        {
            info!("Remove and add back {}.", self.affected_pod_name);
            let validator_name = self.affected_pod_name.as_bytes().to_vec();
            let remove_txn = diem_root_account.sign_with_transaction_builder(
                tx_factory.remove_validator_and_reconfigure(
                    allowed_nonce,
                    validator_name.clone(),
                    self.affected_peer_id,
                ),
            );
            execute_and_wait_transactions(
                &mut full_node_client,
                &mut diem_root_account,
                vec![remove_txn],
            )
            .await?;
            version = expect_epoch(&full_node_client, version, 2).await?;
            let add_txn = diem_root_account.sign_with_transaction_builder(
                tx_factory.add_validator_and_reconfigure(
                    allowed_nonce,
                    validator_name.clone(),
                    self.affected_peer_id,
                ),
            );
            execute_and_wait_transactions(
                &mut full_node_client,
                &mut diem_root_account,
                vec![add_txn],
            )
            .await?;
            version = expect_epoch(&full_node_client, version, 3).await?;
        }

        {
            info!("Switch decoupled-execution on and off repetitively.");
            let upgrade_config = OnChainConsensusConfig::V2(ConsensusConfigV2 {
                two_chain: true,
                decoupled_execution: true,
                back_pressure_limit: 10,
                exclude_round: 20,
            });
            let downgrade_config = OnChainConsensusConfig::default();
            for i in 1..self.count / 2 {
                let upgrade_txn = diem_root_account.sign_with_transaction_builder(
                    tx_factory.update_diem_consensus_config(
                        allowed_nonce,
                        bcs::to_bytes(&upgrade_config).unwrap(),
                    ),
                );
                execute_and_wait_transactions(
                    &mut full_node_client,
                    &mut diem_root_account,
                    vec![upgrade_txn],
                )
                .await?;
                version = expect_epoch(&full_node_client, version, (i + 1) * 2).await?;
                let downgrade_txn = diem_root_account.sign_with_transaction_builder(
                    tx_factory.update_diem_consensus_config(
                        allowed_nonce,
                        bcs::to_bytes(&downgrade_config).unwrap(),
                    ),
                );
                execute_and_wait_transactions(
                    &mut full_node_client,
                    &mut diem_root_account,
                    vec![downgrade_txn],
                )
                .await?;
                version = expect_epoch(&full_node_client, version, (i + 1) * 2 + 1).await?;
            }
        }

        if self.count % 2 == 1 {
            let magic_number = 42;
            info!("Bump DiemVersion to {}", magic_number);
            let update_txn = diem_root_account.sign_with_transaction_builder(
                TransactionFactory::new(ChainId::test())
                    .update_diem_version(allowed_nonce, magic_number),
            );
            execute_and_wait_transactions(
                &mut full_node_client,
                &mut diem_root_account,
                vec![update_txn],
            )
            .await?;
            expect_epoch(&full_node_client, version, self.count + 1).await?;
        }
        let elapsed = timer.elapsed();
        if let Some(job) = emit_job {
            let stats = context.tx_emitter.stop_job(job).await;
            context
                .report
                .report_txn_stats(self.to_string(), stats, elapsed, "");
        } else {
            context.report.report_text(format!(
                "{} finished in {} seconds",
                self.to_string(),
                elapsed.as_secs()
            ));
        }

        Ok(())
    }

    fn deadline(&self) -> Duration {
        // allow each epoch to take 20 secs
        Duration::from_secs(self.count as u64 * 10)
    }
}

impl fmt::Display for Reconfiguration {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Reconfiguration: total epoch: {}", self.count)
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
    access_path::AccessPath,
    account_address::AccountAddress,
    account_config::AccountResource,
    account_state_blob::AccountStateWithProof,
    contract_event::EventWithProof,
    epoch_change::EpochChangeProof,
    ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
    proof::AccumulatorConsistencyProof,
    transaction::{TransactionListWithProof, TransactionWithProof, Version},
    trusted_state::{TrustedState, TrustedStateChange},
};
use anyhow::{bail, ensure, Result};
#[cfg(any(test, feature = "fuzzing"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
use std::{cmp, convert::TryFrom, mem};

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct UpdateToLatestLedgerRequest {
    pub client_known_version: u64,
    pub requested_items: Vec<RequestItem>,
}

impl UpdateToLatestLedgerRequest {
    pub fn new(client_known_version: u64, requested_items: Vec<RequestItem>) -> Self {
        UpdateToLatestLedgerRequest {
            client_known_version,
            requested_items,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct UpdateToLatestLedgerResponse {
    pub response_items: Vec<ResponseItem>,
    pub ledger_info_with_sigs: LedgerInfoWithSignatures,
    pub epoch_change_proof: EpochChangeProof,
    pub ledger_consistency_proof: AccumulatorConsistencyProof,
}

impl UpdateToLatestLedgerResponse {
    /// Constructor.
    pub fn new(
        response_items: Vec<ResponseItem>,
        ledger_info_with_sigs: LedgerInfoWithSignatures,
        epoch_change_proof: EpochChangeProof,
        ledger_consistency_proof: AccumulatorConsistencyProof,
    ) -> Self {
        UpdateToLatestLedgerResponse {
            response_items,
            ledger_info_with_sigs,
            epoch_change_proof,
            ledger_consistency_proof,
        }
    }

    /// Verifies that the response has items corresponding to request items and each of them are
    /// supported by proof it carries and is what the request item asks for.
    ///
    /// After calling this one can trust the info in the response items without further
    /// verification.
    pub fn verify<'a>(
        &'a self,
        trusted_state: &TrustedState,
        request: &UpdateToLatestLedgerRequest,
    ) -> Result<TrustedStateChange<'a>> {
        verify_update_to_latest_ledger_response(
            trusted_state,
            request.client_known_version,
            &request.requested_items,
            &self.response_items,
            &self.ledger_info_with_sigs,
            &self.epoch_change_proof,
        )
    }
}

/// Verifies content of an [`UpdateToLatestLedgerResponse`] against the proofs it
/// carries and the content of the corresponding [`UpdateToLatestLedgerRequest`]
/// Return EpochState if there're epoch change events.
pub fn verify_update_to_latest_ledger_response<'a>(
    trusted_state: &TrustedState,
    req_client_known_version: u64,
    req_request_items: &[RequestItem],
    response_items: &[ResponseItem],
    ledger_info_with_sigs: &'a LedgerInfoWithSignatures,
    epoch_change_proof: &'a EpochChangeProof,
) -> Result<TrustedStateChange<'a>> {
    let ledger_info = ledger_info_with_sigs.ledger_info();

    // Verify that the same or a newer ledger info is returned.
    ensure!(
        ledger_info.version() >= req_client_known_version,
        "Got stale ledger_info with version {}, known version: {}.",
        ledger_info.version(),
        req_client_known_version,
    );

    // Verify any epoch change proof and latest ledger info. Provide an
    // updated trusted state if the proof is correct and not stale.
    let trusted_state_change =
        trusted_state.verify_and_ratchet(ledger_info_with_sigs, epoch_change_proof)?;

    // Verify each sub response.
    ensure!(
        req_request_items.len() == response_items.len(),
        "Number of request items ({}) does not match that of response items ({}).",
        req_request_items.len(),
        response_items.len(),
    );
    itertools::zip_eq(req_request_items, response_items)
        .map(|(req, res)| verify_response_item(ledger_info, req, res))
        .collect::<Result<Vec<_>>>()?;

    Ok(trusted_state_change)
}

fn verify_response_item(
    ledger_info: &LedgerInfo,
    req: &RequestItem,
    res: &ResponseItem,
) -> Result<()> {
    match (req, res) {
        // GetAccountState
        (
            RequestItem::GetAccountState { address },
            ResponseItem::GetAccountState {
                account_state_with_proof,
            },
        ) => account_state_with_proof.verify(ledger_info, ledger_info.version(), *address),
        // GetAccountTransactionBySequenceNumber
        (
            RequestItem::GetAccountTransactionBySequenceNumber {
                account,
                sequence_number,
                fetch_events,
            },
            ResponseItem::GetAccountTransactionBySequenceNumber {
                transaction_with_proof,
                proof_of_current_sequence_number,
            },
        ) => verify_get_txn_by_seq_num_resp(
            ledger_info,
            *account,
            *sequence_number,
            *fetch_events,
            transaction_with_proof.as_ref(),
            proof_of_current_sequence_number.as_ref(),
        ),
        // GetEventsByEventAccessPath
        (
            RequestItem::GetEventsByEventAccessPath {
                access_path,
                start_event_seq_num,
                ascending,
                limit,
            },
            ResponseItem::GetEventsByEventAccessPath {
                events_with_proof,
                proof_of_latest_event,
            },
        ) => verify_get_events_by_access_path_resp(
            ledger_info,
            access_path,
            *start_event_seq_num,
            *ascending,
            *limit,
            events_with_proof,
            proof_of_latest_event,
        ),
        // GetTransactions
        (
            RequestItem::GetTransactions {
                start_version,
                limit,
                fetch_events,
            },
            ResponseItem::GetTransactions {
                txn_list_with_proof,
            },
        ) => verify_get_txns_resp(
            ledger_info,
            *start_version,
            *limit,
            *fetch_events,
            txn_list_with_proof,
        ),
        // Request-response item types mismatch.
        _ => bail!(
            "RequestItem/ResponseItem types mismatch. request: {:?}, response: {:?}",
            mem::discriminant(req),
            mem::discriminant(res),
        ),
    }
}

fn verify_get_txn_by_seq_num_resp(
    ledger_info: &LedgerInfo,
    req_account: AccountAddress,
    req_sequence_number: u64,
    req_fetch_events: bool,
    transaction_with_proof: Option<&TransactionWithProof>,
    proof_of_current_sequence_number: Option<&AccountStateWithProof>,
) -> Result<()> {
    match (transaction_with_proof, proof_of_current_sequence_number) {
        (Some(transaction_with_proof), None) => {
            ensure!(
                req_fetch_events == transaction_with_proof.events.is_some(),
                "Bad GetAccountTxnBySeqNum response. Events requested: {}, events returned: {}.",
                req_fetch_events,
                transaction_with_proof.events.is_some(),
            );
            transaction_with_proof.verify_user_txn(
                ledger_info,
                transaction_with_proof.version,
                req_account,
                req_sequence_number,
            )
        },
        (None, Some(proof_of_current_sequence_number)) => {
            let sequence_number_in_ledger = {
                if let Some(blob) = &proof_of_current_sequence_number.blob {
                    AccountResource::try_from(blob)?.sequence_number()
                } else {
                    // Account does not exist. From the sequence number perspective, it's
                    // equivalent to the situation when the account does exist but has never sent
                    // a transaction. Use default value of sequence number.
                    0
                }
            };
            ensure!(
                sequence_number_in_ledger <= req_sequence_number,
                "Server returned no transactions while it should. Seq num requested: {}, latest seq num in ledger: {}.",
                req_sequence_number,
                sequence_number_in_ledger
            );
            proof_of_current_sequence_number.verify(ledger_info, ledger_info.version(), req_account)
        },
        _ => bail!(
            "Bad GetAccountTxnBySeqNum response. txn_proof.is_none():{}, cur_seq_num_proof.is_none():{}",
            transaction_with_proof.is_none(),
            proof_of_current_sequence_number.is_none(),
        )
    }
}

fn verify_get_events_by_access_path_resp(
    ledger_info: &LedgerInfo,
    req_access_path: &AccessPath,
    req_start_seq_num: u64,
    req_ascending: bool,
    req_limit: u64,
    events_with_proof: &[EventWithProof],
    proof_of_latest_event: &AccountStateWithProof,
) -> Result<()> {
    proof_of_latest_event.verify(ledger_info, ledger_info.version(), req_access_path.address)?;

    let (expected_event_key_opt, seq_num_upper_bound) =
        proof_of_latest_event.get_event_key_and_count_by_query_path(&req_access_path.path)?;

    let cursor =
        if !req_ascending && req_start_seq_num == u64::max_value() && seq_num_upper_bound > 0 {
            seq_num_upper_bound - 1
        } else {
            req_start_seq_num
        };

    let expected_seq_nums = if cursor >= seq_num_upper_bound {
        // Unreachable, so empty.
        Vec::new()
    } else if req_ascending {
        // Ascending, from start to upper bound or limit.
        (cursor..cmp::min(cursor + req_limit, seq_num_upper_bound)).collect()
    } else if cursor + 1 < req_limit {
        // Descending and hitting 0.
        (0..=cursor).rev().collect()
    } else {
        // Descending and hitting limit.
        (cursor + 1 - req_limit..=cursor).rev().collect()
    };

    ensure!(
        expected_seq_nums.len() == events_with_proof.len(),
        "Expecting {} events, got {}.",
        expected_seq_nums.len(),
        events_with_proof.len(),
    );
    if let Some(expected_event_key) = expected_event_key_opt {
        itertools::zip_eq(events_with_proof, expected_seq_nums)
            .map(|(e, seq_num)| {
                e.verify(
                    ledger_info,
                    &expected_event_key,
                    seq_num,
                    e.transaction_version,
                    e.event_index,
                )
            })
            .collect::<Result<Vec<_>>>()?;
    } else if !events_with_proof.is_empty() {
        bail!("Bad events_with_proof: nonempty event list for nonexistent account")
    } // else, empty event list for nonexistent account, which is fine

    Ok(())
}

fn verify_get_txns_resp(
    ledger_info: &LedgerInfo,
    req_start_version: Version,
    req_limit: u64,
    req_fetch_events: bool,
    txn_list_with_proof: &TransactionListWithProof,
) -> Result<()> {
    if req_limit == 0 || req_start_version > ledger_info.version() {
        txn_list_with_proof.verify(ledger_info, None)
    } else {
        ensure!(
            req_fetch_events == txn_list_with_proof.events.is_some(),
            "Bad GetTransactions response. Events requested: {}, events returned: {}.",
            req_fetch_events,
            txn_list_with_proof.events.is_some(),
        );
        let num_txns = txn_list_with_proof.transactions.len();
        ensure!(
            cmp::min(req_limit, ledger_info.version() - req_start_version + 1) == num_txns as u64,
            "Number of transactions returned not expected. num_txns: {}, start version: {}, \
             latest version: {}",
            num_txns,
            req_start_version,
            ledger_info.version(),
        );
        txn_list_with_proof.verify(ledger_info, Some(req_start_version))
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub enum RequestItem {
    GetAccountTransactionBySequenceNumber {
        account: AccountAddress,
        sequence_number: u64,
        fetch_events: bool,
    },
    // this can't be the first variant, tracked here https://github.com/AltSysrq/proptest/issues/141
    GetAccountState {
        address: AccountAddress,
    },
    GetEventsByEventAccessPath {
        access_path: AccessPath,
        start_event_seq_num: u64,
        ascending: bool,
        limit: u64,
    },
    GetTransactions {
        start_version: Version,
        limit: u64,
        fetch_events: bool,
    },
}

#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub enum ResponseItem {
    GetAccountTransactionBySequenceNumber {
        transaction_with_proof: Option<TransactionWithProof>,
        proof_of_current_sequence_number: Option<AccountStateWithProof>,
    },
    // this can't be the first variant, tracked here https://github.com/AltSysrq/proptest/issues/141
    GetAccountState {
        account_state_with_proof: AccountStateWithProof,
    },
    GetEventsByEventAccessPath {
        events_with_proof: Vec<EventWithProof>,
        // TODO: Rename this field to proof_of_event_handle.
        proof_of_latest_event: AccountStateWithProof,
    },
    GetTransactions {
        txn_list_with_proof: TransactionListWithProof,
    },
}

impl ResponseItem {
    pub fn into_get_account_txn_by_seq_num_response(
        self,
    ) -> Result<(Option<TransactionWithProof>, Option<AccountStateWithProof>)> {
        match self {
            ResponseItem::GetAccountTransactionBySequenceNumber {
                transaction_with_proof,
                proof_of_current_sequence_number,
            } => Ok((transaction_with_proof, proof_of_current_sequence_number)),
            _ => bail!("Not ResponseItem::GetAccountTransactionBySequenceNumber."),
        }
    }

    pub fn into_get_transactions_response(self) -> Result<TransactionListWithProof> {
        match self {
            ResponseItem::GetTransactions {
                txn_list_with_proof,
            } => Ok(txn_list_with_proof),
            _ => bail!("Not ResponseItem::GetTransactions."),
        }
    }
}
<commit_msg>Removing into_get_transactions_response from types/src/get_with_proof.rs<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
    access_path::AccessPath,
    account_address::AccountAddress,
    account_config::AccountResource,
    account_state_blob::AccountStateWithProof,
    contract_event::EventWithProof,
    epoch_change::EpochChangeProof,
    ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
    proof::AccumulatorConsistencyProof,
    transaction::{TransactionListWithProof, TransactionWithProof, Version},
    trusted_state::{TrustedState, TrustedStateChange},
};
use anyhow::{bail, ensure, Result};
#[cfg(any(test, feature = "fuzzing"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
use std::{cmp, convert::TryFrom, mem};

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct UpdateToLatestLedgerRequest {
    pub client_known_version: u64,
    pub requested_items: Vec<RequestItem>,
}

impl UpdateToLatestLedgerRequest {
    pub fn new(client_known_version: u64, requested_items: Vec<RequestItem>) -> Self {
        UpdateToLatestLedgerRequest {
            client_known_version,
            requested_items,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct UpdateToLatestLedgerResponse {
    pub response_items: Vec<ResponseItem>,
    pub ledger_info_with_sigs: LedgerInfoWithSignatures,
    pub epoch_change_proof: EpochChangeProof,
    pub ledger_consistency_proof: AccumulatorConsistencyProof,
}

impl UpdateToLatestLedgerResponse {
    /// Constructor.
    pub fn new(
        response_items: Vec<ResponseItem>,
        ledger_info_with_sigs: LedgerInfoWithSignatures,
        epoch_change_proof: EpochChangeProof,
        ledger_consistency_proof: AccumulatorConsistencyProof,
    ) -> Self {
        UpdateToLatestLedgerResponse {
            response_items,
            ledger_info_with_sigs,
            epoch_change_proof,
            ledger_consistency_proof,
        }
    }

    /// Verifies that the response has items corresponding to request items and each of them are
    /// supported by proof it carries and is what the request item asks for.
    ///
    /// After calling this one can trust the info in the response items without further
    /// verification.
    pub fn verify<'a>(
        &'a self,
        trusted_state: &TrustedState,
        request: &UpdateToLatestLedgerRequest,
    ) -> Result<TrustedStateChange<'a>> {
        verify_update_to_latest_ledger_response(
            trusted_state,
            request.client_known_version,
            &request.requested_items,
            &self.response_items,
            &self.ledger_info_with_sigs,
            &self.epoch_change_proof,
        )
    }
}

/// Verifies content of an [`UpdateToLatestLedgerResponse`] against the proofs it
/// carries and the content of the corresponding [`UpdateToLatestLedgerRequest`]
/// Return EpochState if there're epoch change events.
pub fn verify_update_to_latest_ledger_response<'a>(
    trusted_state: &TrustedState,
    req_client_known_version: u64,
    req_request_items: &[RequestItem],
    response_items: &[ResponseItem],
    ledger_info_with_sigs: &'a LedgerInfoWithSignatures,
    epoch_change_proof: &'a EpochChangeProof,
) -> Result<TrustedStateChange<'a>> {
    let ledger_info = ledger_info_with_sigs.ledger_info();

    // Verify that the same or a newer ledger info is returned.
    ensure!(
        ledger_info.version() >= req_client_known_version,
        "Got stale ledger_info with version {}, known version: {}.",
        ledger_info.version(),
        req_client_known_version,
    );

    // Verify any epoch change proof and latest ledger info. Provide an
    // updated trusted state if the proof is correct and not stale.
    let trusted_state_change =
        trusted_state.verify_and_ratchet(ledger_info_with_sigs, epoch_change_proof)?;

    // Verify each sub response.
    ensure!(
        req_request_items.len() == response_items.len(),
        "Number of request items ({}) does not match that of response items ({}).",
        req_request_items.len(),
        response_items.len(),
    );
    itertools::zip_eq(req_request_items, response_items)
        .map(|(req, res)| verify_response_item(ledger_info, req, res))
        .collect::<Result<Vec<_>>>()?;

    Ok(trusted_state_change)
}

fn verify_response_item(
    ledger_info: &LedgerInfo,
    req: &RequestItem,
    res: &ResponseItem,
) -> Result<()> {
    match (req, res) {
        // GetAccountState
        (
            RequestItem::GetAccountState { address },
            ResponseItem::GetAccountState {
                account_state_with_proof,
            },
        ) => account_state_with_proof.verify(ledger_info, ledger_info.version(), *address),
        // GetAccountTransactionBySequenceNumber
        (
            RequestItem::GetAccountTransactionBySequenceNumber {
                account,
                sequence_number,
                fetch_events,
            },
            ResponseItem::GetAccountTransactionBySequenceNumber {
                transaction_with_proof,
                proof_of_current_sequence_number,
            },
        ) => verify_get_txn_by_seq_num_resp(
            ledger_info,
            *account,
            *sequence_number,
            *fetch_events,
            transaction_with_proof.as_ref(),
            proof_of_current_sequence_number.as_ref(),
        ),
        // GetEventsByEventAccessPath
        (
            RequestItem::GetEventsByEventAccessPath {
                access_path,
                start_event_seq_num,
                ascending,
                limit,
            },
            ResponseItem::GetEventsByEventAccessPath {
                events_with_proof,
                proof_of_latest_event,
            },
        ) => verify_get_events_by_access_path_resp(
            ledger_info,
            access_path,
            *start_event_seq_num,
            *ascending,
            *limit,
            events_with_proof,
            proof_of_latest_event,
        ),
        // GetTransactions
        (
            RequestItem::GetTransactions {
                start_version,
                limit,
                fetch_events,
            },
            ResponseItem::GetTransactions {
                txn_list_with_proof,
            },
        ) => verify_get_txns_resp(
            ledger_info,
            *start_version,
            *limit,
            *fetch_events,
            txn_list_with_proof,
        ),
        // Request-response item types mismatch.
        _ => bail!(
            "RequestItem/ResponseItem types mismatch. request: {:?}, response: {:?}",
            mem::discriminant(req),
            mem::discriminant(res),
        ),
    }
}

fn verify_get_txn_by_seq_num_resp(
    ledger_info: &LedgerInfo,
    req_account: AccountAddress,
    req_sequence_number: u64,
    req_fetch_events: bool,
    transaction_with_proof: Option<&TransactionWithProof>,
    proof_of_current_sequence_number: Option<&AccountStateWithProof>,
) -> Result<()> {
    match (transaction_with_proof, proof_of_current_sequence_number) {
        (Some(transaction_with_proof), None) => {
            ensure!(
                req_fetch_events == transaction_with_proof.events.is_some(),
                "Bad GetAccountTxnBySeqNum response. Events requested: {}, events returned: {}.",
                req_fetch_events,
                transaction_with_proof.events.is_some(),
            );
            transaction_with_proof.verify_user_txn(
                ledger_info,
                transaction_with_proof.version,
                req_account,
                req_sequence_number,
            )
        },
        (None, Some(proof_of_current_sequence_number)) => {
            let sequence_number_in_ledger = {
                if let Some(blob) = &proof_of_current_sequence_number.blob {
                    AccountResource::try_from(blob)?.sequence_number()
                } else {
                    // Account does not exist. From the sequence number perspective, it's
                    // equivalent to the situation when the account does exist but has never sent
                    // a transaction. Use default value of sequence number.
                    0
                }
            };
            ensure!(
                sequence_number_in_ledger <= req_sequence_number,
                "Server returned no transactions while it should. Seq num requested: {}, latest seq num in ledger: {}.",
                req_sequence_number,
                sequence_number_in_ledger
            );
            proof_of_current_sequence_number.verify(ledger_info, ledger_info.version(), req_account)
        },
        _ => bail!(
            "Bad GetAccountTxnBySeqNum response. txn_proof.is_none():{}, cur_seq_num_proof.is_none():{}",
            transaction_with_proof.is_none(),
            proof_of_current_sequence_number.is_none(),
        )
    }
}

fn verify_get_events_by_access_path_resp(
    ledger_info: &LedgerInfo,
    req_access_path: &AccessPath,
    req_start_seq_num: u64,
    req_ascending: bool,
    req_limit: u64,
    events_with_proof: &[EventWithProof],
    proof_of_latest_event: &AccountStateWithProof,
) -> Result<()> {
    proof_of_latest_event.verify(ledger_info, ledger_info.version(), req_access_path.address)?;

    let (expected_event_key_opt, seq_num_upper_bound) =
        proof_of_latest_event.get_event_key_and_count_by_query_path(&req_access_path.path)?;

    let cursor =
        if !req_ascending && req_start_seq_num == u64::max_value() && seq_num_upper_bound > 0 {
            seq_num_upper_bound - 1
        } else {
            req_start_seq_num
        };

    let expected_seq_nums = if cursor >= seq_num_upper_bound {
        // Unreachable, so empty.
        Vec::new()
    } else if req_ascending {
        // Ascending, from start to upper bound or limit.
        (cursor..cmp::min(cursor + req_limit, seq_num_upper_bound)).collect()
    } else if cursor + 1 < req_limit {
        // Descending and hitting 0.
        (0..=cursor).rev().collect()
    } else {
        // Descending and hitting limit.
        (cursor + 1 - req_limit..=cursor).rev().collect()
    };

    ensure!(
        expected_seq_nums.len() == events_with_proof.len(),
        "Expecting {} events, got {}.",
        expected_seq_nums.len(),
        events_with_proof.len(),
    );
    if let Some(expected_event_key) = expected_event_key_opt {
        itertools::zip_eq(events_with_proof, expected_seq_nums)
            .map(|(e, seq_num)| {
                e.verify(
                    ledger_info,
                    &expected_event_key,
                    seq_num,
                    e.transaction_version,
                    e.event_index,
                )
            })
            .collect::<Result<Vec<_>>>()?;
    } else if !events_with_proof.is_empty() {
        bail!("Bad events_with_proof: nonempty event list for nonexistent account")
    } // else, empty event list for nonexistent account, which is fine

    Ok(())
}

fn verify_get_txns_resp(
    ledger_info: &LedgerInfo,
    req_start_version: Version,
    req_limit: u64,
    req_fetch_events: bool,
    txn_list_with_proof: &TransactionListWithProof,
) -> Result<()> {
    if req_limit == 0 || req_start_version > ledger_info.version() {
        txn_list_with_proof.verify(ledger_info, None)
    } else {
        ensure!(
            req_fetch_events == txn_list_with_proof.events.is_some(),
            "Bad GetTransactions response. Events requested: {}, events returned: {}.",
            req_fetch_events,
            txn_list_with_proof.events.is_some(),
        );
        let num_txns = txn_list_with_proof.transactions.len();
        ensure!(
            cmp::min(req_limit, ledger_info.version() - req_start_version + 1) == num_txns as u64,
            "Number of transactions returned not expected. num_txns: {}, start version: {}, \
             latest version: {}",
            num_txns,
            req_start_version,
            ledger_info.version(),
        );
        txn_list_with_proof.verify(ledger_info, Some(req_start_version))
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub enum RequestItem {
    GetAccountTransactionBySequenceNumber {
        account: AccountAddress,
        sequence_number: u64,
        fetch_events: bool,
    },
    // this can't be the first variant, tracked here https://github.com/AltSysrq/proptest/issues/141
    GetAccountState {
        address: AccountAddress,
    },
    GetEventsByEventAccessPath {
        access_path: AccessPath,
        start_event_seq_num: u64,
        ascending: bool,
        limit: u64,
    },
    GetTransactions {
        start_version: Version,
        limit: u64,
        fetch_events: bool,
    },
}

#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub enum ResponseItem {
    GetAccountTransactionBySequenceNumber {
        transaction_with_proof: Option<TransactionWithProof>,
        proof_of_current_sequence_number: Option<AccountStateWithProof>,
    },
    // this can't be the first variant, tracked here https://github.com/AltSysrq/proptest/issues/141
    GetAccountState {
        account_state_with_proof: AccountStateWithProof,
    },
    GetEventsByEventAccessPath {
        events_with_proof: Vec<EventWithProof>,
        // TODO: Rename this field to proof_of_event_handle.
        proof_of_latest_event: AccountStateWithProof,
    },
    GetTransactions {
        txn_list_with_proof: TransactionListWithProof,
    },
}

impl ResponseItem {
    pub fn into_get_account_txn_by_seq_num_response(
        self,
    ) -> Result<(Option<TransactionWithProof>, Option<AccountStateWithProof>)> {
        match self {
            ResponseItem::GetAccountTransactionBySequenceNumber {
                transaction_with_proof,
                proof_of_current_sequence_number,
            } => Ok((transaction_with_proof, proof_of_current_sequence_number)),
            _ => bail!("Not ResponseItem::GetAccountTransactionBySequenceNumber."),
        }
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::{
    completion::on_completion,
    goto_definition::on_goto_definition,
    hover::on_hover,
    lsp::{
        set_ready_status, set_starting_status, CompletionOptions, Connection, GotoDefinition,
        HoverRequest, InitializeParams, Message, ServerCapabilities, ServerResponse,
        TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
    },
    lsp_process_error::{LSPProcessError, LSPProcessResult},
    references::on_references,
    shutdown::{on_exit, on_shutdown},
    status_reporting::LSPStatusReporter,
    text_documents::on_did_save_text_document,
    text_documents::{
        on_did_change_text_document, on_did_close_text_document, on_did_open_text_document,
    },
};
use common::{PerfLogEvent, PerfLogger};
use crossbeam::{SendError, Sender};
use log::info;
use lsp_server::{ErrorCode, Notification, ResponseError};
use lsp_types::{
    notification::{
        DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument, Exit,
    },
    request::{Completion, References, Shutdown},
};
use relay_compiler::config::Config;
use std::sync::Arc;
mod lsp_request_dispatch;
use lsp_request_dispatch::LSPRequestDispatch;
mod lsp_notification_dispatch;
use lsp_notification_dispatch::LSPNotificationDispatch;
mod lsp_state;
pub use lsp_state::LSPExtraDataProvider;
pub(crate) use lsp_state::LSPState;

/// Initializes an LSP connection, handling the `initialize` message and `initialized` notification
/// handshake.
pub fn initialize(connection: &Connection) -> LSPProcessResult<InitializeParams> {
    let mut server_capabilities = ServerCapabilities::default();
    // Enable text document syncing so we can know when files are opened/changed/saved/closed
    server_capabilities.text_document_sync =
        Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::Full));

    server_capabilities.completion_provider = Some(CompletionOptions {
        resolve_provider: Some(true),
        trigger_characters: Some(vec!["(".into(), "{".into(), "\n".into(), ",".into()]),
        work_done_progress_options: WorkDoneProgressOptions {
            work_done_progress: None,
        },
    });

    server_capabilities.hover_provider = Some(true);
    server_capabilities.definition_provider = Some(true);
    server_capabilities.references_provider = Some(true);

    let server_capabilities = serde_json::to_value(&server_capabilities)?;
    let params = connection.initialize(server_capabilities)?;
    let params: InitializeParams = serde_json::from_value(params)?;
    Ok(params)
}

/// Run the main server loop
pub async fn run<TPerfLogger: PerfLogger + 'static>(
    connection: Connection,
    mut config: Config,
    _params: InitializeParams,
    perf_logger: Arc<TPerfLogger>,
    extra_data_provider: Box<dyn LSPExtraDataProvider + Send + Sync>,
) -> LSPProcessResult<()>
where
    TPerfLogger: PerfLogger + 'static,
{
    info!(
        "Running language server with config root {:?}",
        config.root_dir
    );
    set_starting_status(&connection.sender);

    config.status_reporter = Box::new(LSPStatusReporter::new(
        config.root_dir.clone(),
        connection.sender.clone(),
    ));

    let mut lsp_state = LSPState::create_state(
        Arc::new(config),
        extra_data_provider,
        Arc::clone(&perf_logger),
    )
    .await?;

    // At this point we're ready to provide hover/complete/go_to_definition capabilities
    set_ready_status(&connection.sender);

    // And now we can listen for messages and notification
    for msg in connection.receiver {
        info!("LSP message received {:?}", msg);
        match msg {
            Message::Request(req) => {
                handle_request(&mut lsp_state, req, &connection.sender, &perf_logger)
                    .map_err(LSPProcessError::from)?;
            }
            Message::Notification(notification) => {
                handle_notification(&mut lsp_state, notification, &perf_logger);
            }
            _ => {
                // Ignore responses for now
            }
        }
    }

    Ok(())
}

fn handle_request<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    request: lsp_server::Request,
    sender: &Sender<Message>,
    perf_logger: &Arc<TPerfLogger>,
) -> Result<(), SendError<Message>> {
    let get_server_response_bound = |req| dispatch_request(req, lsp_state);
    let get_response = with_request_logging(perf_logger, get_server_response_bound);

    sender.send(Message::Response(get_response(request)))
}

fn dispatch_request<TPerfLogger: PerfLogger + 'static>(
    request: lsp_server::Request,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> ServerResponse {
    let get_response = || -> Result<_, ServerResponse> {
        let request = LSPRequestDispatch::new(request, lsp_state)
            .on_request_sync::<HoverRequest>(on_hover)?
            .on_request_sync::<GotoDefinition>(on_goto_definition)?
            .on_request_sync::<References>(on_references)?
            .on_request_sync::<Completion>(on_completion)?
            .on_request_sync::<Shutdown>(on_shutdown)?
            .request();

        // If we have gotten here, we have not handled the request
        Ok(ServerResponse {
            id: request.id,
            result: None,
            error: Some(ResponseError {
                code: ErrorCode::MethodNotFound as i32,
                data: None,
                message: format!("No handler registered for method '{}'", request.method),
            }),
        })
    };
    get_response().unwrap_or_else(|response| response)
}

fn with_request_logging<'a, TPerfLogger: PerfLogger + 'static>(
    perf_logger: &'a Arc<TPerfLogger>,
    get_response: impl FnOnce(lsp_server::Request) -> ServerResponse + 'a,
) -> impl FnOnce(lsp_server::Request) -> ServerResponse + 'a {
    move |request| {
        let lsp_request_event = perf_logger.create_event("lsp_message");
        lsp_request_event.string("lsp_method", request.method.clone());
        lsp_request_event.string("lsp_type", "request".to_string());
        let lsp_request_processing_time = lsp_request_event.start("lsp_message_processing_time");

        let response = get_response(request);

        if response.result.is_some() {
            lsp_request_event.string("lsp_outcome", "success".to_string());
        } else if let Some(error) = &response.error {
            lsp_request_event.string("lsp_outcome", "error".to_string());
            lsp_request_event.number("lsp_error_code", error.code as usize);
            if let Some(data) = &error.data {
                lsp_request_event.string("lsp_error_data", data.to_string());
            }
        }
        // N.B. we don't handle the case where the ServerResponse has neither a result nor
        // an error, which is an invalid state.

        lsp_request_event.stop(lsp_request_processing_time);
        perf_logger.complete_event(lsp_request_event);

        response
    }
}

fn handle_notification<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    notification: Notification,
    perf_logger: &Arc<TPerfLogger>,
) {
    let lsp_notification_event = perf_logger.create_event("lsp_message");
    lsp_notification_event.string("lsp_method", notification.method.clone());
    lsp_notification_event.string("lsp_type", "notification".to_string());
    let lsp_notification_processing_time =
        lsp_notification_event.start("lsp_message_processing_time");

    let notification_result = dispatch_notification(notification, lsp_state);

    // The only possible error (for now) is not handling the notification.
    // N.B. is_ok is correct here.
    if notification_result.is_ok() {
        lsp_notification_event.string("lsp_outcome", "error".to_string());
        lsp_notification_event.number("lsp_error_code", ErrorCode::MethodNotFound as usize);
    }

    lsp_notification_event.stop(lsp_notification_processing_time);
    perf_logger.complete_event(lsp_notification_event);
}

fn dispatch_notification<TPerfLogger: PerfLogger + 'static>(
    notification: lsp_server::Notification,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> Result<(), ()> {
    let notification = LSPNotificationDispatch::new(notification, lsp_state)
        .on_notification_sync::<DidOpenTextDocument>(on_did_open_text_document)?
        .on_notification_sync::<DidCloseTextDocument>(on_did_close_text_document)?
        .on_notification_sync::<DidChangeTextDocument>(on_did_change_text_document)?
        .on_notification_sync::<DidSaveTextDocument>(on_did_save_text_document)?
        .on_notification_sync::<Exit>(on_exit)?
        .notification();

    // If we have gotten here, we have not handled the notification
    info!(
        "Error: no handler registered for notification '{}'",
        notification.method
    );
    Ok(())
}
<commit_msg>Don't log lsp_error_code<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::{
    completion::on_completion,
    goto_definition::on_goto_definition,
    hover::on_hover,
    lsp::{
        set_ready_status, set_starting_status, CompletionOptions, Connection, GotoDefinition,
        HoverRequest, InitializeParams, Message, ServerCapabilities, ServerResponse,
        TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
    },
    lsp_process_error::{LSPProcessError, LSPProcessResult},
    references::on_references,
    shutdown::{on_exit, on_shutdown},
    status_reporting::LSPStatusReporter,
    text_documents::on_did_save_text_document,
    text_documents::{
        on_did_change_text_document, on_did_close_text_document, on_did_open_text_document,
    },
};
use common::{PerfLogEvent, PerfLogger};
use crossbeam::{SendError, Sender};
use log::info;
use lsp_server::{ErrorCode, Notification, ResponseError};
use lsp_types::{
    notification::{
        DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument, Exit,
    },
    request::{Completion, References, Shutdown},
};
use relay_compiler::config::Config;
use std::sync::Arc;
mod lsp_request_dispatch;
use lsp_request_dispatch::LSPRequestDispatch;
mod lsp_notification_dispatch;
use lsp_notification_dispatch::LSPNotificationDispatch;
mod lsp_state;
pub use lsp_state::LSPExtraDataProvider;
pub(crate) use lsp_state::LSPState;

/// Initializes an LSP connection, handling the `initialize` message and `initialized` notification
/// handshake.
pub fn initialize(connection: &Connection) -> LSPProcessResult<InitializeParams> {
    let mut server_capabilities = ServerCapabilities::default();
    // Enable text document syncing so we can know when files are opened/changed/saved/closed
    server_capabilities.text_document_sync =
        Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::Full));

    server_capabilities.completion_provider = Some(CompletionOptions {
        resolve_provider: Some(true),
        trigger_characters: Some(vec!["(".into(), "{".into(), "\n".into(), ",".into()]),
        work_done_progress_options: WorkDoneProgressOptions {
            work_done_progress: None,
        },
    });

    server_capabilities.hover_provider = Some(true);
    server_capabilities.definition_provider = Some(true);
    server_capabilities.references_provider = Some(true);

    let server_capabilities = serde_json::to_value(&server_capabilities)?;
    let params = connection.initialize(server_capabilities)?;
    let params: InitializeParams = serde_json::from_value(params)?;
    Ok(params)
}

/// Run the main server loop
pub async fn run<TPerfLogger: PerfLogger + 'static>(
    connection: Connection,
    mut config: Config,
    _params: InitializeParams,
    perf_logger: Arc<TPerfLogger>,
    extra_data_provider: Box<dyn LSPExtraDataProvider + Send + Sync>,
) -> LSPProcessResult<()>
where
    TPerfLogger: PerfLogger + 'static,
{
    info!(
        "Running language server with config root {:?}",
        config.root_dir
    );
    set_starting_status(&connection.sender);

    config.status_reporter = Box::new(LSPStatusReporter::new(
        config.root_dir.clone(),
        connection.sender.clone(),
    ));

    let mut lsp_state = LSPState::create_state(
        Arc::new(config),
        extra_data_provider,
        Arc::clone(&perf_logger),
    )
    .await?;

    // At this point we're ready to provide hover/complete/go_to_definition capabilities
    set_ready_status(&connection.sender);

    // And now we can listen for messages and notification
    for msg in connection.receiver {
        info!("LSP message received {:?}", msg);
        match msg {
            Message::Request(req) => {
                handle_request(&mut lsp_state, req, &connection.sender, &perf_logger)
                    .map_err(LSPProcessError::from)?;
            }
            Message::Notification(notification) => {
                handle_notification(&mut lsp_state, notification, &perf_logger);
            }
            _ => {
                // Ignore responses for now
            }
        }
    }

    Ok(())
}

fn handle_request<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    request: lsp_server::Request,
    sender: &Sender<Message>,
    perf_logger: &Arc<TPerfLogger>,
) -> Result<(), SendError<Message>> {
    let get_server_response_bound = |req| dispatch_request(req, lsp_state);
    let get_response = with_request_logging(perf_logger, get_server_response_bound);

    sender.send(Message::Response(get_response(request)))
}

fn dispatch_request<TPerfLogger: PerfLogger + 'static>(
    request: lsp_server::Request,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> ServerResponse {
    let get_response = || -> Result<_, ServerResponse> {
        let request = LSPRequestDispatch::new(request, lsp_state)
            .on_request_sync::<HoverRequest>(on_hover)?
            .on_request_sync::<GotoDefinition>(on_goto_definition)?
            .on_request_sync::<References>(on_references)?
            .on_request_sync::<Completion>(on_completion)?
            .on_request_sync::<Shutdown>(on_shutdown)?
            .request();

        // If we have gotten here, we have not handled the request
        Ok(ServerResponse {
            id: request.id,
            result: None,
            error: Some(ResponseError {
                code: ErrorCode::MethodNotFound as i32,
                data: None,
                message: format!("No handler registered for method '{}'", request.method),
            }),
        })
    };
    get_response().unwrap_or_else(|response| response)
}

fn with_request_logging<'a, TPerfLogger: PerfLogger + 'static>(
    perf_logger: &'a Arc<TPerfLogger>,
    get_response: impl FnOnce(lsp_server::Request) -> ServerResponse + 'a,
) -> impl FnOnce(lsp_server::Request) -> ServerResponse + 'a {
    move |request| {
        let lsp_request_event = perf_logger.create_event("lsp_message");
        lsp_request_event.string("lsp_method", request.method.clone());
        lsp_request_event.string("lsp_type", "request".to_string());
        let lsp_request_processing_time = lsp_request_event.start("lsp_message_processing_time");

        let response = get_response(request);

        if response.result.is_some() {
            lsp_request_event.string("lsp_outcome", "success".to_string());
        } else if let Some(error) = &response.error {
            lsp_request_event.string("lsp_outcome", "error".to_string());
            if let Some(data) = &error.data {
                lsp_request_event.string("lsp_error_data", data.to_string());
            }
        }
        // N.B. we don't handle the case where the ServerResponse has neither a result nor
        // an error, which is an invalid state.

        lsp_request_event.stop(lsp_request_processing_time);
        perf_logger.complete_event(lsp_request_event);

        response
    }
}

fn handle_notification<TPerfLogger: PerfLogger + 'static>(
    lsp_state: &mut LSPState<TPerfLogger>,
    notification: Notification,
    perf_logger: &Arc<TPerfLogger>,
) {
    let lsp_notification_event = perf_logger.create_event("lsp_message");
    lsp_notification_event.string("lsp_method", notification.method.clone());
    lsp_notification_event.string("lsp_type", "notification".to_string());
    let lsp_notification_processing_time =
        lsp_notification_event.start("lsp_message_processing_time");

    let notification_result = dispatch_notification(notification, lsp_state);

    // The only possible error (for now) is not handling the notification.
    // N.B. is_ok is correct here.
    if notification_result.is_ok() {
        lsp_notification_event.string("lsp_outcome", "error".to_string());
    }

    lsp_notification_event.stop(lsp_notification_processing_time);
    perf_logger.complete_event(lsp_notification_event);
}

fn dispatch_notification<TPerfLogger: PerfLogger + 'static>(
    notification: lsp_server::Notification,
    lsp_state: &mut LSPState<TPerfLogger>,
) -> Result<(), ()> {
    let notification = LSPNotificationDispatch::new(notification, lsp_state)
        .on_notification_sync::<DidOpenTextDocument>(on_did_open_text_document)?
        .on_notification_sync::<DidCloseTextDocument>(on_did_close_text_document)?
        .on_notification_sync::<DidChangeTextDocument>(on_did_change_text_document)?
        .on_notification_sync::<DidSaveTextDocument>(on_did_save_text_document)?
        .on_notification_sync::<Exit>(on_exit)?
        .notification();

    // If we have gotten here, we have not handled the notification
    info!(
        "Error: no handler registered for notification '{}'",
        notification.method
    );
    Ok(())
}
<|endoftext|>


<commit_before>//! Lane-wise arithmetic operations.
#![allow(unused)]

macro_rules! impl_int_minmax_ops {
    ($id:ident) => {
        impl $id {
            // Note:
            //
            // * if two elements are equal min returns
            //   always the second element
            // * if two elements are equal max returns
            //   always the second element
            //
            // Since we are dealing with integers here, and `min` and `max`
            // construct a new integer vector, whether the first or the
            // second element is returned when two elements compare equal
            // does not matter.

            /// Minimum of two vectors.
            ///
            /// Returns a new vector containing the minimum value of each of
            /// the input vector lanes.
            #[inline]
            pub fn min(self, x: Self) -> Self {
                self.lt(x).select(self, x)
            }

            /// Maximum of two vectors.
            ///
            /// Returns a new vector containing the minimum value of each of
            /// the input vector lanes.
            #[inline]
            pub fn max(self, x: Self) -> Self {
                self.gt(x).select(self, x)
            }
        }
    };
}

#[cfg(test)]
macro_rules! test_int_minmax_ops {
    ($id:ident, $elem_ty:ident) => {
        #[test]
        fn minmax() {
            use coresimd::simd::$id;
            let o = $id::splat(1 as $elem_ty);
            let t = $id::splat(2 as $elem_ty);

            let mut m = o;
            for i in 0..$id::lanes() {
                if i % 2 == 0 {
                    m = m.replace(i, 2 as $elem_ty);
                }
            }

            assert_eq!(o.min(t), o);
            assert_eq!(t.min(o), o);
            assert_eq!(m.min(o), o);
            assert_eq!(o.min(m), o);
            assert_eq!(m.min(t), m);
            assert_eq!(t.min(m), m);

            assert_eq!(o.max(t), t);
            assert_eq!(t.max(o), t);
            assert_eq!(m.max(o), m);
            assert_eq!(o.max(m), m);
            assert_eq!(m.max(t), t);
            assert_eq!(t.max(m), t);
        }
    };
}

macro_rules! impl_float_minmax_ops {
    ($id:ident) => {
        impl $id {
            /// Minimum of two vectors.
            ///
            /// Returns a new vector containing the minimum value of each of the
            /// input vector lanes. The lane-wise semantics are the same as that
            /// of `min` for the primitive floating-point types.
            #[inline]
            pub fn min(self, x: Self) -> Self {
                use coresimd::simd_llvm::simd_fmin;
                unsafe { simd_fmin(self, x) }
            }

            /// Maximum of two vectors.
            ///
            /// Returns a new vector containing the minimum value of each of the
            /// input vector lanes. The lane-wise semantics are the same as that
            /// of `max` for the primitive floating-point types.
            #[inline]
            pub fn max(self, x: Self) -> Self {
                // FIXME: https://github.com/rust-lang-nursery/stdsimd/issues/416
                // use coresimd::simd_llvm::simd_fmax;
                // unsafe { simd_fmax(self, x) }
                let mut r = self;
                for i in 0..$id::lanes() {
                    let a = self.extract(i);
                    let b = x.extract(i);
                    r = r.replace(i, a.max(b))
                }
                r
            }
        }
    }
}

#[cfg(test)]
macro_rules! test_float_minmax_ops {
    ($id:ident, $elem_ty:ident) => {
        #[test]
        fn minmax() {
            use coresimd::simd::$id;
            let n = ::std::$elem_ty::NAN;
            let o = $id::splat(1. as $elem_ty);
            let t = $id::splat(2. as $elem_ty);

            let mut m = o;
            let mut on = o;
            for i in 0..$id::lanes() {
                if i % 2 == 0 {
                    m = m.replace(i, 2. as $elem_ty);
                    on = on.replace(i, n);
                }
            }

            assert_eq!(o.min(t), o);
            assert_eq!(t.min(o), o);
            assert_eq!(m.min(o), o);
            assert_eq!(o.min(m), o);
            assert_eq!(m.min(t), m);
            assert_eq!(t.min(m), m);

            assert_eq!(o.max(t), t);
            assert_eq!(t.max(o), t);
            assert_eq!(m.max(o), m);
            assert_eq!(o.max(m), m);
            assert_eq!(m.max(t), t);
            assert_eq!(t.max(m), t);

            assert_eq!(on.min(o), o);
            assert_eq!(o.min(on), o);
            assert_eq!(on.max(o), o);
            assert_eq!(o.max(on), o);
        }
    };
}
<commit_msg>Fix typo in `max` docs<commit_after>//! Lane-wise arithmetic operations.
#![allow(unused)]

macro_rules! impl_int_minmax_ops {
    ($id:ident) => {
        impl $id {
            // Note:
            //
            // * if two elements are equal min returns
            //   always the second element
            // * if two elements are equal max returns
            //   always the second element
            //
            // Since we are dealing with integers here, and `min` and `max`
            // construct a new integer vector, whether the first or the
            // second element is returned when two elements compare equal
            // does not matter.

            /// Minimum of two vectors.
            ///
            /// Returns a new vector containing the minimum value of each of
            /// the input vector lanes.
            #[inline]
            pub fn min(self, x: Self) -> Self {
                self.lt(x).select(self, x)
            }

            /// Maximum of two vectors.
            ///
            /// Returns a new vector containing the maximum value of each of
            /// the input vector lanes.
            #[inline]
            pub fn max(self, x: Self) -> Self {
                self.gt(x).select(self, x)
            }
        }
    };
}

#[cfg(test)]
macro_rules! test_int_minmax_ops {
    ($id:ident, $elem_ty:ident) => {
        #[test]
        fn minmax() {
            use coresimd::simd::$id;
            let o = $id::splat(1 as $elem_ty);
            let t = $id::splat(2 as $elem_ty);

            let mut m = o;
            for i in 0..$id::lanes() {
                if i % 2 == 0 {
                    m = m.replace(i, 2 as $elem_ty);
                }
            }

            assert_eq!(o.min(t), o);
            assert_eq!(t.min(o), o);
            assert_eq!(m.min(o), o);
            assert_eq!(o.min(m), o);
            assert_eq!(m.min(t), m);
            assert_eq!(t.min(m), m);

            assert_eq!(o.max(t), t);
            assert_eq!(t.max(o), t);
            assert_eq!(m.max(o), m);
            assert_eq!(o.max(m), m);
            assert_eq!(m.max(t), t);
            assert_eq!(t.max(m), t);
        }
    };
}

macro_rules! impl_float_minmax_ops {
    ($id:ident) => {
        impl $id {
            /// Minimum of two vectors.
            ///
            /// Returns a new vector containing the minimum value of each of the
            /// input vector lanes. The lane-wise semantics are the same as that
            /// of `min` for the primitive floating-point types.
            #[inline]
            pub fn min(self, x: Self) -> Self {
                use coresimd::simd_llvm::simd_fmin;
                unsafe { simd_fmin(self, x) }
            }

            /// Maximum of two vectors.
            ///
            /// Returns a new vector containing the maximum value of each of the
            /// input vector lanes. The lane-wise semantics are the same as that
            /// of `max` for the primitive floating-point types.
            #[inline]
            pub fn max(self, x: Self) -> Self {
                // FIXME: https://github.com/rust-lang-nursery/stdsimd/issues/416
                // use coresimd::simd_llvm::simd_fmax;
                // unsafe { simd_fmax(self, x) }
                let mut r = self;
                for i in 0..$id::lanes() {
                    let a = self.extract(i);
                    let b = x.extract(i);
                    r = r.replace(i, a.max(b))
                }
                r
            }
        }
    }
}

#[cfg(test)]
macro_rules! test_float_minmax_ops {
    ($id:ident, $elem_ty:ident) => {
        #[test]
        fn minmax() {
            use coresimd::simd::$id;
            let n = ::std::$elem_ty::NAN;
            let o = $id::splat(1. as $elem_ty);
            let t = $id::splat(2. as $elem_ty);

            let mut m = o;
            let mut on = o;
            for i in 0..$id::lanes() {
                if i % 2 == 0 {
                    m = m.replace(i, 2. as $elem_ty);
                    on = on.replace(i, n);
                }
            }

            assert_eq!(o.min(t), o);
            assert_eq!(t.min(o), o);
            assert_eq!(m.min(o), o);
            assert_eq!(o.min(m), o);
            assert_eq!(m.min(t), m);
            assert_eq!(t.min(m), m);

            assert_eq!(o.max(t), t);
            assert_eq!(t.max(o), t);
            assert_eq!(m.max(o), m);
            assert_eq!(o.max(m), m);
            assert_eq!(m.max(t), t);
            assert_eq!(t.max(m), t);

            assert_eq!(on.min(o), o);
            assert_eq!(o.min(on), o);
            assert_eq!(on.max(o), o);
            assert_eq!(o.max(on), o);
        }
    };
}
<|endoftext|>