Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

light-client-cli: add command to fetch archive blocks and verify them #3399

Merged
merged 7 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion ledger/sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rust-version = { workspace = true }
[[bin]]
name = "ledger-sync-test-app"
path = "src/test_app/main.rs"
required-features = ["mc-consensus-enclave-measurement"]

[dependencies]
mc-account-keys = { path = "../../account-keys" }
Expand All @@ -19,7 +20,7 @@ mc-blockchain-test-utils = { path = "../../blockchain/test-utils" }
mc-blockchain-types = { path = "../../blockchain/types" }
mc-common = { path = "../../common", features = ["log"] }
mc-connection = { path = "../../connection" }
mc-consensus-enclave-measurement = { path = "../../consensus/enclave/measurement" }
mc-consensus-enclave-measurement = { path = "../../consensus/enclave/measurement", optional = true }
mc-consensus-scp = { path = "../../consensus/scp" }
mc-ledger-db = { path = "../../ledger/db" }
mc-transaction-core = { path = "../../transaction/core" }
Expand Down
3 changes: 3 additions & 0 deletions light-client/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ path = "src/bin/main.rs"

[dependencies]
mc-api = { path = "../../api" }
mc-blockchain-types = { path = "../../blockchain/types" }
mc-common = { path = "../../common", features = ["log"] }
mc-consensus-api = { path = "../../consensus/api" }
mc-consensus-scp-types = { path = "../../consensus/scp/types" }
mc-ledger-sync = { path = "../../ledger/sync" }
mc-light-client-verifier = { path = "../verifier" }
mc-util-grpc = { path = "../../util/grpc" }
mc-util-uri = { path = "../../util/uri" }

clap = { version = "4.1", features = ["derive", "env"] }
grpcio = "0.12.1"
protobuf = "2.27.1"
serde_json = "1.0"
219 changes: 158 additions & 61 deletions light-client/cli/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,61 @@

use clap::{Parser, Subcommand};
use grpcio::{ChannelBuilder, EnvBuilder};
use mc_api::blockchain::ArchiveBlocks;
use mc_blockchain_types::BlockIndex;
use mc_common::{
logger::{create_app_logger, o},
logger::{create_app_logger, log, o, Logger},
ResponderId,
};
use mc_consensus_api::{
consensus_client_grpc::ConsensusClientApiClient, consensus_common_grpc::BlockchainApiClient,
};
use mc_consensus_scp_types::QuorumSet;
use mc_ledger_sync::ReqwestTransactionsFetcher;
use mc_light_client_verifier::{
HexKeyNodeID, LightClientVerifierConfig, TrustedValidatorSetConfig,
HexKeyNodeID, LightClientVerifier, LightClientVerifierConfig, TrustedValidatorSetConfig,
};
use mc_util_grpc::ConnectionUriGrpcioChannel;
use mc_util_uri::ConsensusClientUri;
use std::{str::FromStr, sync::Arc};
use protobuf::Message;
use std::{fs, path::PathBuf, str::FromStr, sync::Arc};

#[derive(Subcommand)]
pub enum Commands {
/// Generate a light client verifier config from a list of nodes.
/// This does not include any historical data.
GenerateConfig {
/// Node URIs to use for generating the config.
#[clap(long = "node", use_value_delimiter = true, env = "MC_NODES")]
nodes: Vec<ConsensusClientUri>,
},

/// Fetch one or more `[ArchiveBlock]`s from a list of tx source urls and
/// store them in a Protobuf file.
FetchArchiveBlocks {
/// URLs to use for fetching blocks.
///
/// For example: https://ledger.mobilecoinww.com/node1.prod.mobilecoinww.com
#[clap(
long = "tx-source-url",
use_value_delimiter = true,
env = "MC_TX_SOURCE_URL"
)]
tx_source_urls: Vec<String>,

/// Block index we are interested in.
#[clap(long, env = "MC_BLOCK_INDEX")]
block_index: BlockIndex,

/// File to write the fetched ArchiveBlocks protobuf to.
#[clap(long, env = "MC_OUT_FILE")]
out_file: PathBuf,

eranrund marked this conversation as resolved.
Show resolved Hide resolved
/// Optional LightClientVerifierConfig to use for verifying the fetched
/// blocks before writing them to disk.
#[clap(long, env = "MC_LIGHT_CLIENT_VERIFIER_CONFIG")]
light_client_verifier_config: Option<PathBuf>,
},
}

#[derive(Parser)]
Expand All @@ -39,67 +73,130 @@ fn main() {
let (logger, _global_logger_guard) = create_app_logger(o!());
let config = Config::parse();

let env = Arc::new(EnvBuilder::new().name_prefix("light-client-grpc").build());

match config.command {
Commands::GenerateConfig { nodes } => {
let (node_configs, last_block_infos): (Vec<_>, Vec<_>) = nodes
.iter()
.map(|node_uri| {
// TODO should this use ThickClient and chain-id?
let ch = ChannelBuilder::default_channel_builder(env.clone())
.connect_to_uri(node_uri, &logger);

let client_api = ConsensusClientApiClient::new(ch.clone());
let config = client_api
.get_node_config(&Default::default())
.expect("get_node_config failed");

let blockchain_api = BlockchainApiClient::new(ch);
let last_block_info = blockchain_api
.get_last_block_info(&Default::default())
.expect("get_last_block_info failed");

(config, last_block_info)
})
.unzip();

let node_ids = node_configs
.iter()
.map(|node_config| HexKeyNodeID {
responder_id: ResponderId::from_str(node_config.get_peer_responder_id())
.unwrap(),
public_key: node_config
.get_scp_message_signing_key()
.try_into()
.unwrap(),
})
.collect::<Vec<_>>();

let quorum_set = QuorumSet {
threshold: node_configs.len() as u32,
members: node_ids.into_iter().map(Into::into).collect(),
};

let trusted_validator_set = TrustedValidatorSetConfig { quorum_set };

let trusted_validator_set_start_block = last_block_infos
.iter()
.map(|last_block_info| last_block_info.index)
.max()
.unwrap_or_default();

let light_client_verifier = LightClientVerifierConfig {
trusted_validator_set,
trusted_validator_set_start_block,
historical_validator_sets: Default::default(),
known_valid_block_ids: Default::default(),
};

println!(
"{}",
serde_json::to_string_pretty(&light_client_verifier).unwrap()
cmd_generate_config(nodes, logger);
}

Commands::FetchArchiveBlocks {
tx_source_urls,
block_index,
out_file,
light_client_verifier_config,
} => {
cmd_fetch_archive_blocks(
tx_source_urls,
block_index,
out_file,
light_client_verifier_config,
logger,
);
}
}
}

fn cmd_generate_config(nodes: Vec<ConsensusClientUri>, logger: Logger) {
let env = Arc::new(EnvBuilder::new().name_prefix("light-client-grpc").build());

let (node_configs, last_block_infos): (Vec<_>, Vec<_>) = nodes
.iter()
.map(|node_uri| {
// TODO should this use ThickClient and chain-id?
let ch = ChannelBuilder::default_channel_builder(env.clone())
.connect_to_uri(node_uri, &logger);

let client_api = ConsensusClientApiClient::new(ch.clone());
let config = client_api
.get_node_config(&Default::default())
.expect("get_node_config failed");

let blockchain_api = BlockchainApiClient::new(ch);
let last_block_info = blockchain_api
.get_last_block_info(&Default::default())
.expect("get_last_block_info failed");

(config, last_block_info)
})
.unzip();

let node_ids = node_configs
.iter()
.map(|node_config| HexKeyNodeID {
responder_id: ResponderId::from_str(node_config.get_peer_responder_id()).unwrap(),
public_key: node_config
.get_scp_message_signing_key()
.try_into()
.unwrap(),
})
.collect::<Vec<_>>();

let quorum_set = QuorumSet {
threshold: node_configs.len() as u32,
members: node_ids.into_iter().map(Into::into).collect(),
};

let trusted_validator_set = TrustedValidatorSetConfig { quorum_set };

let trusted_validator_set_start_block = last_block_infos
.iter()
.map(|last_block_info| last_block_info.index)
.max()
.unwrap_or_default();

let light_client_verifier = LightClientVerifierConfig {
trusted_validator_set,
trusted_validator_set_start_block,
historical_validator_sets: Default::default(),
known_valid_block_ids: Default::default(),
};

println!(
"{}",
serde_json::to_string_pretty(&light_client_verifier).unwrap()
);
}
eranrund marked this conversation as resolved.
Show resolved Hide resolved

fn cmd_fetch_archive_blocks(
tx_source_urls: Vec<String>,
block_index: u64,
out_file: PathBuf,
light_client_verifier_config_path: Option<PathBuf>,
logger: Logger,
) {
let block_data = tx_source_urls
.into_iter()
.map(|url| {
log::info!(logger, "Fetching block data from {}", url);
let rts = ReqwestTransactionsFetcher::new(vec![url], logger.clone())
.expect("failed creating ReqwestTransactionsFetcher");
rts.get_block_data_by_index(block_index, None)
.expect("failed fetching block data")
})
.collect::<Vec<_>>();

if let Some(path) = light_client_verifier_config_path {
let json_data =
fs::read_to_string(path).expect("failed reading LightClientVerifierConfig file");
let light_client_verifier_config: LightClientVerifierConfig =
serde_json::from_str(&json_data).expect("failed parsing LightClientVerifierConfig");
let light_client_verifer = LightClientVerifier::from(light_client_verifier_config);
eranrund marked this conversation as resolved.
Show resolved Hide resolved

light_client_verifer
.verify_block_data(&block_data[..])
.expect("failed verifying block data");
}

let archive_blocks = ArchiveBlocks::from(&block_data[..]);
let bytes = archive_blocks
.write_to_bytes()
.expect("failed serializing ArchiveBlocks");
fs::write(&out_file, bytes).expect("failed writing ArchiveBlocks to file");
log::info!(
logger,
"Wrote ArchiveBlocks to file {}",
out_file.to_string_lossy()
);

// Give the logger time to flush :/
std::thread::sleep(std::time::Duration::from_millis(100));
}
4 changes: 4 additions & 0 deletions light-client/verifier/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ pub enum Error {
BlockContentHashMismatch(BlockContentsHash),
/// TxOut (public key {0:?}) was not found among the block contents
TxOutNotFound([u8; 32]),
/// Not all BlockDatas point at the same block
BlockDataMismatch,
/// No block data was provided
NoBlockData,
}
Loading