From 2b9d3e729a09e16cfeb9c4009654e00afb976aa0 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Fri, 22 Dec 2023 12:59:56 +0100 Subject: [PATCH 01/31] Added verify-contract command --- .vscode/launch.json | 64 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 5 ++++ src/verify_contract.rs | 37 ++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 src/verify_contract.rs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..102f5ae1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,64 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'casper_client'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=casper-client" + ], + "filter": { + "name": "casper_client", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'casper-client'", + "cargo": { + "args": [ + "build", + "--bin=casper-client", + "--package=casper-client" + ], + "filter": { + "name": "casper-client", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'casper-client'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=casper-client", + "--package=casper-client" + ], + "filter": { + "name": "casper-client", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d67770b9..46ce3096 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ mod keygen; mod list_rpcs; mod query_balance; mod query_global_state; +mod verify_contract; use std::process; @@ -49,6 +50,7 @@ use keygen::Keygen; use list_rpcs::ListRpcs; use query_balance::QueryBalance; use query_global_state::QueryGlobalState; +use verify_contract::VerifyContract; const APP_NAME: &str = "Casper client"; @@ -95,6 +97,7 @@ enum DisplayOrder { Keygen, AccountAddress, GenerateCompletion, + VerifyContract, } fn cli() -> Command { @@ -140,6 +143,7 @@ fn cli() -> Command { .subcommand(GenerateCompletion::build( DisplayOrder::GenerateCompletion as usize, )) + .subcommand(VerifyContract::build(DisplayOrder::VerifyContract as usize)) } #[tokio::main(flavor = "current_thread")] @@ -179,6 +183,7 @@ async fn main() { Keygen::NAME => Keygen::run(matches).await, AccountAddress::NAME => AccountAddress::run(matches).await, GenerateCompletion::NAME => GenerateCompletion::run(matches).await, + VerifyContract::NAME => VerifyContract::run(matches).await, _ => { let _ = cli().print_long_help(); println!(); diff --git a/src/verify_contract.rs b/src/verify_contract.rs new file mode 100644 index 00000000..50f11b5c --- /dev/null +++ b/src/verify_contract.rs @@ -0,0 +1,37 @@ +use std::str; + +use async_trait::async_trait; +use clap::{ArgMatches, Command}; + +use casper_client::cli::CliError; + +use crate::{command::ClientCommand, common, Success}; + +pub struct VerifyContract; + +/// This struct defines the order in which the args are shown for this subcommand's help message. +enum DisplayOrder { + Verbose, + BlockIdentifier, + PublicKey +} + +#[async_trait] +impl ClientCommand for VerifyContract { + const NAME: &'static str = "verify-contract"; + const ABOUT: &'static str = "Verify smart contract source code"; + + fn build(display_order: usize) -> Command { + Command::new(Self::NAME) + .about(Self::ABOUT) + .display_order(display_order) + .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) + .arg(common::block_identifier::arg(DisplayOrder::BlockIdentifier as usize, true)) + .arg(common::public_key::arg(DisplayOrder::PublicKey as usize, true)) + + } + + async fn run(matches: &ArgMatches) -> Result { + Ok(Success::Output("OK".to_string())) + } +} From b9149a984142b1c94523ba97f831a7181a5f665c Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Fri, 22 Dec 2023 13:30:00 +0100 Subject: [PATCH 02/31] Added verify contract to lib --- lib/cli.rs | 9 +++++++++ lib/lib.rs | 12 ++++++++++++ src/verify_contract.rs | 1 - 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/cli.rs b/lib/cli.rs index 19102854..49b85807 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -708,3 +708,12 @@ pub async fn get_era_info( .await .map_err(CliError::from) } + +/// Verifies the smart contract code againt the one deployed at address. +pub async fn verify_contract( + block_identifier: &str, + public_key: &str, + verbosity_level: u64, +) -> Result<(), CliError> { + crate::verify_contract(block_identifier, public_key, verbosity_level).await.map_err(CliError::from) +} diff --git a/lib/lib.rs b/lib/lib.rs index 94e83cf4..abe90550 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -554,3 +554,15 @@ pub async fn get_era_info( .send_request(GET_ERA_INFO_METHOD, params) .await } + +/// Verifies the smart contract code againt the one deployed at address. +pub async fn verify_contract( + block_identifier: &str, + public_key: &str, + verbosity_level: u64, +) -> Result<(), Error> { + println!("Block indentifer: {}", block_identifier); + println!("Public key: {}", public_key); + println!("Verbosity level: {}", verbosity_level); + Ok(()) +} diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 50f11b5c..cd88a2ca 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -28,7 +28,6 @@ impl ClientCommand for VerifyContract { .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) .arg(common::block_identifier::arg(DisplayOrder::BlockIdentifier as usize, true)) .arg(common::public_key::arg(DisplayOrder::PublicKey as usize, true)) - } async fn run(matches: &ArgMatches) -> Result { From f87937840204b9dc2f2e27298c2b0ebf907150f2 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Tue, 2 Jan 2024 10:27:37 +0100 Subject: [PATCH 03/31] Added function for creating archive --- .vscode/launch.json | 64 --------------------------------------------- lib/verification.rs | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 64 deletions(-) delete mode 100644 .vscode/launch.json create mode 100644 lib/verification.rs diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 102f5ae1..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'casper_client'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=casper-client" - ], - "filter": { - "name": "casper_client", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'casper-client'", - "cargo": { - "args": [ - "build", - "--bin=casper-client", - "--package=casper-client" - ], - "filter": { - "name": "casper-client", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'casper-client'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=casper-client", - "--package=casper-client" - ], - "filter": { - "name": "casper-client", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/lib/verification.rs b/lib/verification.rs new file mode 100644 index 00000000..cb5084fc --- /dev/null +++ b/lib/verification.rs @@ -0,0 +1,27 @@ +static GIT_DIR_NAME: &str = ".git"; +static TARGET_DIR_NAME: &str = "target"; + +/// Build tar-gzip archive from files in the provided path. +pub fn build_archive(path: &Path) -> Result { + let buffer = BytesMut::new().writer(); + let encoder = GzEncoder::new(buffer, Compression::best()); + let mut archive = TarBuilder::new(encoder); + + for entry in (path.read_dir()?).flatten() { + let file_name = entry.file_name(); + // Skip `.git` and `target`. + if file_name == TARGET_DIR_NAME || file_name == GIT_DIR_NAME { + continue; + } + let full_path = entry.path(); + if full_path.is_dir() { + archive.append_dir_all(&file_name, &full_path)?; + } else { + archive.append_path_with_name(&full_path, &file_name)?; + } + } + + let encoder = archive.into_inner()?; + let buffer = encoder.finish()?; + Ok(buffer.into_inner().freeze()) +} From d0c624135dc816dd64f9d1cab5cb1ddfe7e2b706 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Tue, 2 Jan 2024 12:17:46 +0100 Subject: [PATCH 04/31] Added arg parsing --- lib/cli.rs | 7 +++++-- lib/lib.rs | 4 ++-- src/verify_contract.rs | 34 +++++++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/cli.rs b/lib/cli.rs index 49b85807..70632796 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -37,6 +37,7 @@ mod tests; use serde::Serialize; use casper_hashing::Digest; +use casper_types::PublicKey; use casper_types::URef; #[cfg(doc)] use casper_types::{account::AccountHash, Key}; @@ -712,8 +713,10 @@ pub async fn get_era_info( /// Verifies the smart contract code againt the one deployed at address. pub async fn verify_contract( block_identifier: &str, - public_key: &str, + public_key: PublicKey, verbosity_level: u64, ) -> Result<(), CliError> { - crate::verify_contract(block_identifier, public_key, verbosity_level).await.map_err(CliError::from) + crate::verify_contract(block_identifier, public_key, verbosity_level) + .await + .map_err(CliError::from) } diff --git a/lib/lib.rs b/lib/lib.rs index abe90550..9f16b35c 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -64,7 +64,7 @@ use casper_hashing::Digest; use casper_types::SecretKey; #[cfg(doc)] use casper_types::Transfer; -use casper_types::{Key, URef}; +use casper_types::{Key, URef, PublicKey}; pub use error::Error; use json_rpc::JsonRpcCall; @@ -558,7 +558,7 @@ pub async fn get_era_info( /// Verifies the smart contract code againt the one deployed at address. pub async fn verify_contract( block_identifier: &str, - public_key: &str, + public_key: PublicKey, verbosity_level: u64, ) -> Result<(), Error> { println!("Block indentifer: {}", block_identifier); diff --git a/src/verify_contract.rs b/src/verify_contract.rs index cd88a2ca..39b34b7c 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -1,11 +1,16 @@ use std::str; use async_trait::async_trait; +use casper_types::{AsymmetricType, PublicKey}; use clap::{ArgMatches, Command}; use casper_client::cli::CliError; -use crate::{command::ClientCommand, common, Success}; +use crate::{ + command::ClientCommand, + common::{self, block_identifier}, + Success, +}; pub struct VerifyContract; @@ -13,7 +18,7 @@ pub struct VerifyContract; enum DisplayOrder { Verbose, BlockIdentifier, - PublicKey + PublicKey, } #[async_trait] @@ -26,11 +31,30 @@ impl ClientCommand for VerifyContract { .about(Self::ABOUT) .display_order(display_order) .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) - .arg(common::block_identifier::arg(DisplayOrder::BlockIdentifier as usize, true)) - .arg(common::public_key::arg(DisplayOrder::PublicKey as usize, true)) + .arg(common::block_identifier::arg( + DisplayOrder::BlockIdentifier as usize, + true, + )) + .arg(common::public_key::arg( + DisplayOrder::PublicKey as usize, + true, + )) } async fn run(matches: &ArgMatches) -> Result { - Ok(Success::Output("OK".to_string())) + let block_identifier = common::block_identifier::get(matches); + let hex_public_key = common::public_key::get(matches, true)?; + let public_key = PublicKey::from_hex(&hex_public_key).map_err(|error| { + eprintln!("Can't parse {} as a public key: {}", hex_public_key, error); + CliError::FailedToParsePublicKey { + context: "account-address".to_string(), + error, + } + })?; + let verbosity_level = common::verbose::get(matches); + + casper_client::cli::verify_contract(block_identifier, public_key, verbosity_level) + .await + .map(Success::from) } } From bf44ebbb63467e9886a52d0e2e06c3e9b4989340 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Tue, 2 Jan 2024 17:05:56 +0100 Subject: [PATCH 05/31] Added verificator api client --- Cargo.toml | 5 +++++ lib/lib.rs | 33 +++++++++++++++++++++++++++++++-- lib/verification.rs | 6 ++++++ src/verify_contract.rs | 2 +- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c6a4cfa9..625f4153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,14 @@ std-fs-io = ["casper-types/std-fs-io"] [dependencies] async-trait = { version = "0.1.74", optional = true } base16 = "0.2.1" +base64 = "0.21.5" +bytes = "1.5.0" casper-hashing = "3.0.0" casper-types = { version = "4.0.1", features = ["std"] } +openapi = { path = "../Casper-SCVS-Validator/openapi" } clap = { version = "4.4.10", optional = true, features = ["cargo", "deprecated", "wrap_help"] } clap_complete = { version = "4.4.4", optional = true } +flate2 = "1.0.28" hex-buffer-serde = "0.4.0" humantime = "2.1.0" itertools = "0.12.0" @@ -43,6 +47,7 @@ reqwest = { version = "0.11.22", features = ["json"] } schemars = "=0.8.5" serde = { version = "1.0.193", default-features = false, features = ["derive"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } +tar = "0.4.40" thiserror = "1.0.50" tokio = { version = "1.34.0", optional = true, features = ["macros", "rt", "sync", "time"] } uint = "0.9.5" diff --git a/lib/lib.rs b/lib/lib.rs index 9f16b35c..d6df2fb4 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -48,7 +48,9 @@ mod transfer_target; pub mod types; mod validation; mod verbosity; +mod verification; +use std::env::current_dir; #[cfg(feature = "std-fs-io")] use std::{ fs, @@ -64,11 +66,15 @@ use casper_hashing::Digest; use casper_types::SecretKey; #[cfg(doc)] use casper_types::Transfer; -use casper_types::{Key, URef, PublicKey}; +use casper_types::{Key, PublicKey, URef}; pub use error::Error; use json_rpc::JsonRpcCall; pub use json_rpc::{JsonRpcId, SuccessResponse}; +use openapi::{ + apis::{configuration::Configuration, default_api::verification_post}, + models::VerificationRequest, +}; #[cfg(feature = "std-fs-io")] pub use output_kind::OutputKind; use rpcs::{ @@ -111,6 +117,9 @@ use types::{Account, Block, StoredValue}; use types::{Deploy, DeployHash}; pub use validation::ValidateResponseError; pub use verbosity::Verbosity; +pub use verification::build_archive; + +use base64::{engine::general_purpose, Engine as _}; /// Puts a [`Deploy`] to the network for execution. /// @@ -562,7 +571,27 @@ pub async fn verify_contract( verbosity_level: u64, ) -> Result<(), Error> { println!("Block indentifer: {}", block_identifier); - println!("Public key: {}", public_key); + println!("Public key: {}", public_key.to_account_hash()); println!("Verbosity level: {}", verbosity_level); + + let project_path = current_dir().expect("Error getting current directory"); + + println!("Project path: {}", project_path.display()); + let archive = build_archive(&project_path).unwrap(); + println!("Archive size {}", archive.len()); + let archive_base64 = general_purpose::STANDARD.encode(&archive); + let verification_request = VerificationRequest { + address: Some(block_identifier.to_string()), + public_key: Some(public_key.to_account_hash().to_string()), // Wrap public_key.to_account_hash() inside Some() and convert it to String + code_archive: Some(archive_base64), + }; + let configuration = Configuration::default(); + let verification_result = verification_post(&configuration, Some(verification_request)) + .await + .unwrap(); + println!( + "Verification result {}", + verification_result.status.unwrap().to_string() + ); Ok(()) } diff --git a/lib/verification.rs b/lib/verification.rs index cb5084fc..f246b28d 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -1,3 +1,9 @@ +use std::path::Path; + +use bytes::{BufMut, Bytes, BytesMut}; +use flate2::{write::GzEncoder, Compression}; +use tar::Builder as TarBuilder; + static GIT_DIR_NAME: &str = ".git"; static TARGET_DIR_NAME: &str = "target"; diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 39b34b7c..99415c6f 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -8,7 +8,7 @@ use casper_client::cli::CliError; use crate::{ command::ClientCommand, - common::{self, block_identifier}, + common::{self}, Success, }; From a6e6360690fb528ee636ff92710b336eb80948a5 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Wed, 3 Jan 2024 15:36:57 +0100 Subject: [PATCH 06/31] Added waiting for verification to finish --- lib/cli.rs | 16 ++++++-- lib/lib.rs | 92 +++++++++++++++++++++++++++++++++--------- src/verify_contract.rs | 46 +++++++++++++++++++-- 3 files changed, 129 insertions(+), 25 deletions(-) diff --git a/lib/cli.rs b/lib/cli.rs index 70632796..bd304b5c 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -33,6 +33,7 @@ mod simple_args; #[cfg(test)] mod tests; +use openapi::models::VerificationResult; #[cfg(feature = "std-fs-io")] use serde::Serialize; @@ -715,8 +716,15 @@ pub async fn verify_contract( block_identifier: &str, public_key: PublicKey, verbosity_level: u64, -) -> Result<(), CliError> { - crate::verify_contract(block_identifier, public_key, verbosity_level) - .await - .map_err(CliError::from) + verification_url_base_path: &str, +) -> Result { + let verbosity = parse::verbosity(verbosity_level); + crate::verify_contract( + block_identifier, + public_key, + verbosity, + verification_url_base_path, + ) + .await + .map_err(CliError::from) } diff --git a/lib/lib.rs b/lib/lib.rs index d6df2fb4..1ecfb3fb 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -72,8 +72,11 @@ pub use error::Error; use json_rpc::JsonRpcCall; pub use json_rpc::{JsonRpcId, SuccessResponse}; use openapi::{ - apis::{configuration::Configuration, default_api::verification_post}, - models::VerificationRequest, + apis::{ + configuration::Configuration, + default_api::{verification_address_status_get, verification_post}, + }, + models::{VerificationRequest, VerificationStatus, VerificationResult}, }; #[cfg(feature = "std-fs-io")] pub use output_kind::OutputKind; @@ -568,30 +571,83 @@ pub async fn get_era_info( pub async fn verify_contract( block_identifier: &str, public_key: PublicKey, - verbosity_level: u64, -) -> Result<(), Error> { - println!("Block indentifer: {}", block_identifier); - println!("Public key: {}", public_key.to_account_hash()); - println!("Verbosity level: {}", verbosity_level); + verbosity: Verbosity, + verification_url_base_path: &str, +) -> Result { + + if verbosity == Verbosity::High { + println!("Block indentifer: {}", block_identifier); + println!("Public key: {}", public_key.to_account_hash()); + println!("Verification URL base path: {}", verification_url_base_path); + } let project_path = current_dir().expect("Error getting current directory"); - println!("Project path: {}", project_path.display()); - let archive = build_archive(&project_path).unwrap(); - println!("Archive size {}", archive.len()); + if verbosity == Verbosity::High { + println!("Project path: {}", project_path.display()); + } + + let archive = build_archive(&project_path).expect("Cannot create project archive"); + + if verbosity == Verbosity::High { + println!("Created project archive - size: {}", archive.len()); + } + let archive_base64 = general_purpose::STANDARD.encode(&archive); + let verification_request = VerificationRequest { address: Some(block_identifier.to_string()), public_key: Some(public_key.to_account_hash().to_string()), // Wrap public_key.to_account_hash() inside Some() and convert it to String code_archive: Some(archive_base64), }; - let configuration = Configuration::default(); - let verification_result = verification_post(&configuration, Some(verification_request)) + + let mut configuration = Configuration::default(); + configuration.base_path = verification_url_base_path.to_string(); + + if verbosity == Verbosity::High { + println!("Sending verfication request to {}", configuration.base_path); + } + + let mut verification_result = verification_post(&configuration, Some(verification_request)) .await - .unwrap(); - println!( - "Verification result {}", - verification_result.status.unwrap().to_string() - ); - Ok(()) + .expect("Cannot send verification request"); + + if verbosity == Verbosity::High { + println!( + "Sent verification request - status {}", + verification_result.status.unwrap().to_string() + ); + } + + let mut verification_status = verification_result.status.unwrap(); + + if verbosity == Verbosity::High { + print!("Waiting for verification to finish...",); + } + + while verification_status != VerificationStatus::Verified + && verification_status != VerificationStatus::Failed + { + verification_result = verification_address_status_get(&configuration, block_identifier) + .await + .unwrap(); + verification_status = verification_result.status.unwrap(); + + if verbosity == Verbosity::High { + print!(".",); + } + } + + if verbosity == Verbosity::High { + println!(""); + } + + if verbosity == Verbosity::High { + println!( + "Verification finished - status {}", + verification_status.to_string() + ); + } + + Ok(verification_result) } diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 99415c6f..e5b0202f 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -12,6 +12,8 @@ use crate::{ Success, }; +use clap::Arg; + pub struct VerifyContract; /// This struct defines the order in which the args are shown for this subcommand's help message. @@ -19,6 +21,35 @@ enum DisplayOrder { Verbose, BlockIdentifier, PublicKey, + VerificationUrlBasePath, +} + +pub mod verification_url_base_path { + use super::*; + + const ARG_NAME: &str = "verification-url-basepath"; + const ARG_SHORT: char = 'u'; + const ARG_VALUE_NAME: &str = "HOST:PORT"; + const ARG_DEFAULT: &str = "http://localhost:8080"; + const ARG_HELP: &str = "Hostname or IP and port of the verification API"; + + pub fn arg(order: usize) -> Arg { + Arg::new(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(false) + .default_value(ARG_DEFAULT) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(order) + } + + pub fn get(matches: &ArgMatches) -> &str { + matches + .get_one::(ARG_NAME) + .map(String::as_str) + .unwrap_or_else(|| panic!("should have {} arg", ARG_NAME)) + } } #[async_trait] @@ -39,6 +70,9 @@ impl ClientCommand for VerifyContract { DisplayOrder::PublicKey as usize, true, )) + .arg(verification_url_base_path::arg( + DisplayOrder::VerificationUrlBasePath as usize, + )) } async fn run(matches: &ArgMatches) -> Result { @@ -52,9 +86,15 @@ impl ClientCommand for VerifyContract { } })?; let verbosity_level = common::verbose::get(matches); + let verification_url_base_path = verification_url_base_path::get(matches); - casper_client::cli::verify_contract(block_identifier, public_key, verbosity_level) - .await - .map(Success::from) + casper_client::cli::verify_contract( + block_identifier, + public_key, + verbosity_level, + verification_url_base_path, + ) + .await + .map(Success::from) } } From ff261d5cf3ec87d2b10851073fae234027c0ab0c Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Wed, 3 Jan 2024 18:30:26 +0100 Subject: [PATCH 07/31] Switched to deploy hash --- lib/cli.rs | 5 +++-- lib/lib.rs | 22 ++++++++++---------- src/account_address.rs | 1 + src/common.rs | 47 +++++++++++++++++++++++++++++++++++------- src/deploy/get.rs | 28 ++----------------------- src/verify_contract.rs | 12 +++++------ 6 files changed, 61 insertions(+), 54 deletions(-) diff --git a/lib/cli.rs b/lib/cli.rs index bd304b5c..79b73632 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -713,14 +713,15 @@ pub async fn get_era_info( /// Verifies the smart contract code againt the one deployed at address. pub async fn verify_contract( - block_identifier: &str, + deploy_hash: &str, public_key: PublicKey, verbosity_level: u64, verification_url_base_path: &str, ) -> Result { let verbosity = parse::verbosity(verbosity_level); + let deploy_hash = parse::deploy_hash(deploy_hash)?; crate::verify_contract( - block_identifier, + deploy_hash, public_key, verbosity, verification_url_base_path, diff --git a/lib/lib.rs b/lib/lib.rs index 1ecfb3fb..2ca3272e 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -76,7 +76,7 @@ use openapi::{ configuration::Configuration, default_api::{verification_address_status_get, verification_post}, }, - models::{VerificationRequest, VerificationStatus, VerificationResult}, + models::{VerificationRequest, VerificationResult, VerificationStatus}, }; #[cfg(feature = "std-fs-io")] pub use output_kind::OutputKind; @@ -569,14 +569,13 @@ pub async fn get_era_info( /// Verifies the smart contract code againt the one deployed at address. pub async fn verify_contract( - block_identifier: &str, + deploy_hash: DeployHash, public_key: PublicKey, verbosity: Verbosity, verification_url_base_path: &str, ) -> Result { - if verbosity == Verbosity::High { - println!("Block indentifer: {}", block_identifier); + println!("Deploy hash: {}", deploy_hash.to_string()); println!("Public key: {}", public_key.to_account_hash()); println!("Verification URL base path: {}", verification_url_base_path); } @@ -592,11 +591,11 @@ pub async fn verify_contract( if verbosity == Verbosity::High { println!("Created project archive - size: {}", archive.len()); } - + let archive_base64 = general_purpose::STANDARD.encode(&archive); let verification_request = VerificationRequest { - address: Some(block_identifier.to_string()), + address: Some(deploy_hash.to_string()), public_key: Some(public_key.to_account_hash().to_string()), // Wrap public_key.to_account_hash() inside Some() and convert it to String code_archive: Some(archive_base64), }; @@ -611,7 +610,7 @@ pub async fn verify_contract( let mut verification_result = verification_post(&configuration, Some(verification_request)) .await .expect("Cannot send verification request"); - + if verbosity == Verbosity::High { println!( "Sent verification request - status {}", @@ -628,11 +627,12 @@ pub async fn verify_contract( while verification_status != VerificationStatus::Verified && verification_status != VerificationStatus::Failed { - verification_result = verification_address_status_get(&configuration, block_identifier) - .await - .unwrap(); + verification_result = + verification_address_status_get(&configuration, deploy_hash.to_string().as_str()) + .await + .unwrap(); verification_status = verification_result.status.unwrap(); - + if verbosity == Verbosity::High { print!(".",); } diff --git a/src/account_address.rs b/src/account_address.rs index e221a0a0..ed68531d 100644 --- a/src/account_address.rs +++ b/src/account_address.rs @@ -29,6 +29,7 @@ impl ClientCommand for AccountAddress { .arg(common::public_key::arg( DisplayOrder::PublicKey as usize, PUBLIC_KEY_IS_REQUIRED, + false, )) } diff --git a/src/common.rs b/src/common.rs index 825cc69e..718e76d8 100644 --- a/src/common.rs +++ b/src/common.rs @@ -226,14 +226,21 @@ pub(super) mod public_key { should be one of the two public key files generated via the `keygen` subcommand; \ \"public_key_hex\" or \"public_key.pem\""; - pub fn arg(order: usize, is_required: bool) -> Arg { - Arg::new(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(is_required) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) - .display_order(order) + pub fn arg(order: usize, is_required: bool, is_positional: bool) -> Arg { + match is_positional { + true => Arg::new(ARG_NAME) + .required(true) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(order), + false => Arg::new(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(is_required) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(order), + } } pub fn get(matches: &ArgMatches, is_required: bool) -> Result { @@ -374,3 +381,27 @@ pub(super) mod purse_uref { matches.get_one::(ARG_NAME).map(String::as_str) } } + +/// Handles providing the arg for and retrieval of the deploy hash. +pub mod deploy_hash { + use super::*; + + const ARG_NAME: &str = "deploy-hash"; + const ARG_VALUE_NAME: &str = "HEX STRING"; + const ARG_HELP: &str = "Hex-encoded deploy hash"; + + pub fn arg(display_order: usize) -> Arg { + Arg::new(ARG_NAME) + .required(true) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(display_order) + } + + pub fn get(matches: &ArgMatches) -> &str { + matches + .get_one::(ARG_NAME) + .map(String::as_str) + .unwrap_or_else(|| panic!("should have {} arg", ARG_NAME)) + } +} diff --git a/src/deploy/get.rs b/src/deploy/get.rs index fbba91a1..3e35ffa2 100644 --- a/src/deploy/get.rs +++ b/src/deploy/get.rs @@ -18,30 +18,6 @@ enum DisplayOrder { FinalizedApprovals, } -/// Handles providing the arg for and retrieval of the deploy hash. -mod deploy_hash { - use super::*; - - const ARG_NAME: &str = "deploy-hash"; - const ARG_VALUE_NAME: &str = "HEX STRING"; - const ARG_HELP: &str = "Hex-encoded deploy hash"; - - pub(super) fn arg() -> Arg { - Arg::new(ARG_NAME) - .required(true) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) - .display_order(DisplayOrder::DeployHash as usize) - } - - pub(super) fn get(matches: &ArgMatches) -> &str { - matches - .get_one::(ARG_NAME) - .map(String::as_str) - .unwrap_or_else(|| panic!("should have {} arg", ARG_NAME)) - } -} - /// Handles providing the arg for the retrieval of the finalized approvals. mod finalized_approvals { use super::*; @@ -85,7 +61,7 @@ impl ClientCommand for GetDeploy { DisplayOrder::NodeAddress as usize, )) .arg(common::rpc_id::arg(DisplayOrder::RpcId as usize)) - .arg(deploy_hash::arg()) + .arg(common::deploy_hash::arg(DisplayOrder::DeployHash as usize)) .arg(finalized_approvals::arg()) } @@ -93,7 +69,7 @@ impl ClientCommand for GetDeploy { let maybe_rpc_id = common::rpc_id::get(matches); let node_address = common::node_address::get(matches); let verbosity_level = common::verbose::get(matches); - let deploy_hash = deploy_hash::get(matches); + let deploy_hash = common::deploy_hash::get(matches); let finalized_approvals = finalized_approvals::get(matches); casper_client::cli::get_deploy( diff --git a/src/verify_contract.rs b/src/verify_contract.rs index e5b0202f..6d7550d1 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -19,7 +19,7 @@ pub struct VerifyContract; /// This struct defines the order in which the args are shown for this subcommand's help message. enum DisplayOrder { Verbose, - BlockIdentifier, + DeployHash, PublicKey, VerificationUrlBasePath, } @@ -62,13 +62,11 @@ impl ClientCommand for VerifyContract { .about(Self::ABOUT) .display_order(display_order) .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) - .arg(common::block_identifier::arg( - DisplayOrder::BlockIdentifier as usize, - true, - )) + .arg(common::deploy_hash::arg(DisplayOrder::DeployHash as usize)) .arg(common::public_key::arg( DisplayOrder::PublicKey as usize, true, + true, )) .arg(verification_url_base_path::arg( DisplayOrder::VerificationUrlBasePath as usize, @@ -76,7 +74,7 @@ impl ClientCommand for VerifyContract { } async fn run(matches: &ArgMatches) -> Result { - let block_identifier = common::block_identifier::get(matches); + let deploy_hash = common::deploy_hash::get(matches); let hex_public_key = common::public_key::get(matches, true)?; let public_key = PublicKey::from_hex(&hex_public_key).map_err(|error| { eprintln!("Can't parse {} as a public key: {}", hex_public_key, error); @@ -89,7 +87,7 @@ impl ClientCommand for VerifyContract { let verification_url_base_path = verification_url_base_path::get(matches); casper_client::cli::verify_contract( - block_identifier, + deploy_hash, public_key, verbosity_level, verification_url_base_path, From e4c24deb0955707e30f8266573fbabf17c52a67b Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Wed, 3 Jan 2024 18:38:49 +0100 Subject: [PATCH 08/31] Updated comments --- lib/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/lib.rs b/lib/lib.rs index 2ca3272e..91ae0ebc 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -567,7 +567,7 @@ pub async fn get_era_info( .await } -/// Verifies the smart contract code againt the one deployed at address. +/// Verifies the smart contract code againt the one deployed at deploy hash. pub async fn verify_contract( deploy_hash: DeployHash, public_key: PublicKey, @@ -595,8 +595,8 @@ pub async fn verify_contract( let archive_base64 = general_purpose::STANDARD.encode(&archive); let verification_request = VerificationRequest { - address: Some(deploy_hash.to_string()), - public_key: Some(public_key.to_account_hash().to_string()), // Wrap public_key.to_account_hash() inside Some() and convert it to String + deploy_hash: Some(deploy_hash.to_string()), + public_key: Some(public_key.to_account_hash().to_string()), code_archive: Some(archive_base64), }; From ffb53e3a2b481cc40d9f31b6043ca91d0daca856 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Fri, 5 Jan 2024 10:28:16 +0100 Subject: [PATCH 09/31] Added getting verification details --- lib/cli.rs | 10 ++-- lib/error.rs | 4 ++ lib/lib.rs | 85 ++++++------------------------ lib/verification.rs | 116 ++++++++++++++++++++++++++++++++++++++++- src/verify_contract.rs | 4 +- 5 files changed, 143 insertions(+), 76 deletions(-) diff --git a/lib/cli.rs b/lib/cli.rs index 79b73632..1e6985ce 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -33,7 +33,7 @@ mod simple_args; #[cfg(test)] mod tests; -use openapi::models::VerificationResult; +use openapi::models::VerificationDetails; #[cfg(feature = "std-fs-io")] use serde::Serialize; @@ -715,16 +715,16 @@ pub async fn get_era_info( pub async fn verify_contract( deploy_hash: &str, public_key: PublicKey, - verbosity_level: u64, verification_url_base_path: &str, -) -> Result { - let verbosity = parse::verbosity(verbosity_level); + verbosity_level: u64, +) -> Result { let deploy_hash = parse::deploy_hash(deploy_hash)?; + let verbosity = parse::verbosity(verbosity_level); crate::verify_contract( deploy_hash, public_key, - verbosity, verification_url_base_path, + verbosity, ) .await .map_err(CliError::from) diff --git a/lib/error.rs b/lib/error.rs index a0970b32..4e8ff8d6 100644 --- a/lib/error.rs +++ b/lib/error.rs @@ -172,6 +172,10 @@ pub enum Error { /// Failed to validate response. #[error("invalid response: {0}")] ResponseFailedValidation(#[from] ValidateResponseError), + + /// Failed to verify contract. + #[error("contract verification failed")] + ContractVerificationFailed, } impl From for Error { diff --git a/lib/lib.rs b/lib/lib.rs index 91ae0ebc..fe47a6b5 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -58,6 +58,7 @@ use std::{ path::Path, }; +use openapi::models::VerificationDetails; #[cfg(feature = "std-fs-io")] use serde::Serialize; @@ -71,13 +72,6 @@ use casper_types::{Key, PublicKey, URef}; pub use error::Error; use json_rpc::JsonRpcCall; pub use json_rpc::{JsonRpcId, SuccessResponse}; -use openapi::{ - apis::{ - configuration::Configuration, - default_api::{verification_address_status_get, verification_post}, - }, - models::{VerificationRequest, VerificationResult, VerificationStatus}, -}; #[cfg(feature = "std-fs-io")] pub use output_kind::OutputKind; use rpcs::{ @@ -120,7 +114,7 @@ use types::{Account, Block, StoredValue}; use types::{Deploy, DeployHash}; pub use validation::ValidateResponseError; pub use verbosity::Verbosity; -pub use verification::build_archive; +pub use verification::{build_archive, send_verification_request}; use base64::{engine::general_purpose, Engine as _}; @@ -571,13 +565,16 @@ pub async fn get_era_info( pub async fn verify_contract( deploy_hash: DeployHash, public_key: PublicKey, - verbosity: Verbosity, verification_url_base_path: &str, -) -> Result { + verbosity: Verbosity, +) -> Result { if verbosity == Verbosity::High { println!("Deploy hash: {}", deploy_hash.to_string()); println!("Public key: {}", public_key.to_account_hash()); - println!("Verification URL base path: {}", verification_url_base_path); + println!( + "Verification service base path: {}", + verification_url_base_path + ); } let project_path = current_dir().expect("Error getting current directory"); @@ -589,65 +586,17 @@ pub async fn verify_contract( let archive = build_archive(&project_path).expect("Cannot create project archive"); if verbosity == Verbosity::High { - println!("Created project archive - size: {}", archive.len()); + println!("Created project archive (size: {})", archive.len()); } let archive_base64 = general_purpose::STANDARD.encode(&archive); - let verification_request = VerificationRequest { - deploy_hash: Some(deploy_hash.to_string()), - public_key: Some(public_key.to_account_hash().to_string()), - code_archive: Some(archive_base64), - }; - - let mut configuration = Configuration::default(); - configuration.base_path = verification_url_base_path.to_string(); - - if verbosity == Verbosity::High { - println!("Sending verfication request to {}", configuration.base_path); - } - - let mut verification_result = verification_post(&configuration, Some(verification_request)) - .await - .expect("Cannot send verification request"); - - if verbosity == Verbosity::High { - println!( - "Sent verification request - status {}", - verification_result.status.unwrap().to_string() - ); - } - - let mut verification_status = verification_result.status.unwrap(); - - if verbosity == Verbosity::High { - print!("Waiting for verification to finish...",); - } - - while verification_status != VerificationStatus::Verified - && verification_status != VerificationStatus::Failed - { - verification_result = - verification_address_status_get(&configuration, deploy_hash.to_string().as_str()) - .await - .unwrap(); - verification_status = verification_result.status.unwrap(); - - if verbosity == Verbosity::High { - print!(".",); - } - } - - if verbosity == Verbosity::High { - println!(""); - } - - if verbosity == Verbosity::High { - println!( - "Verification finished - status {}", - verification_status.to_string() - ); - } - - Ok(verification_result) + send_verification_request( + deploy_hash, + public_key, + verification_url_base_path, + archive_base64, + verbosity, + ) + .await } diff --git a/lib/verification.rs b/lib/verification.rs index f246b28d..6fe3ef5d 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -1,13 +1,38 @@ use std::path::Path; use bytes::{BufMut, Bytes, BytesMut}; +use casper_types::PublicKey; use flate2::{write::GzEncoder, Compression}; +use openapi::{ + apis::{ + configuration::Configuration, + default_api::{ + verification_deploy_hash_details_get, verification_deploy_hash_status_get, + verification_post, + }, + }, + models::{VerificationDetails, VerificationRequest, VerificationStatus}, +}; use tar::Builder as TarBuilder; +use crate::{types::DeployHash, Error, Verbosity}; + static GIT_DIR_NAME: &str = ".git"; static TARGET_DIR_NAME: &str = "target"; -/// Build tar-gzip archive from files in the provided path. +/// Builds an archive from the specified path. +/// +/// This function creates a compressed tar archive from the files and directories located at the +/// specified path. It excludes the `.git` and `target` directories from the archive. +/// +/// # Arguments +/// +/// * `path` - The path to the directory containing the files and directories to be archived. +/// +/// # Returns +/// +/// The compressed tar archive as a `Bytes` object, or an `std::io::Error` if an error occurs during +/// the archiving process. pub fn build_archive(path: &Path) -> Result { let buffer = BytesMut::new().writer(); let encoder = GzEncoder::new(buffer, Compression::best()); @@ -31,3 +56,92 @@ pub fn build_archive(path: &Path) -> Result { let buffer = encoder.finish()?; Ok(buffer.into_inner().freeze()) } + +/// Verifies the smart contract code against the one deployed at deploy hash. +/// +/// Sends a verification request to the specified verification URL base path, including the deploy hash, +/// public key, and code archive. +/// +/// # Arguments +/// +/// * `deploy_hash` - The hash of the deployed contract. +/// * `public_key` - The public key associated with the contract. +/// * `verification_url_base_path` - The base path of the verification URL. +/// * `verbosity` - The verbosity level of the verification process. +/// +/// # Returns +/// +/// The verification details of the contract. +pub async fn send_verification_request( + deploy_hash: DeployHash, + public_key: PublicKey, + verification_url_base_path: &str, + archive_base64: String, + verbosity: Verbosity, +) -> Result { + let verification_request = VerificationRequest { + deploy_hash: Some(deploy_hash.to_string()), + public_key: Some(public_key.to_account_hash().to_string()), + code_archive: Some(archive_base64), + }; + + let mut configuration = Configuration::default(); + configuration.base_path = verification_url_base_path.to_string(); + + if verbosity == Verbosity::High { + println!("Sending verfication request to {}", configuration.base_path); + } + + let mut verification_result = verification_post(&configuration, Some(verification_request)) + .await + .expect("Cannot send verification request"); + + if verbosity == Verbosity::High { + println!( + "Sent verification request - status {}", + verification_result.status.unwrap().to_string() + ); + } + + let mut verification_status = verification_result.status.unwrap(); + + if verbosity == Verbosity::High { + println!("Waiting for verification to finish...",); + } + + while verification_status != VerificationStatus::Verified + && verification_status != VerificationStatus::Failed + { + verification_result = + verification_deploy_hash_status_get(&configuration, deploy_hash.to_string().as_str()) + .await + .expect("Cannot get verification status"); + + verification_status = verification_result.status.unwrap(); + } + + if verbosity == Verbosity::High { + println!( + "Verification finished - status {}", + verification_status.to_string() + ); + } + + if verbosity == Verbosity::High { + println!("Getting verification details..."); + } + + let verification_details = + verification_deploy_hash_details_get(&configuration, deploy_hash.to_string().as_str()) + .await; + + match verification_details { + Ok(verification_details) => Ok(verification_details), + Err(error) => { + if verbosity == Verbosity::High { + println!("Cannot get verification details: {:?}", error); + } + Err(Error::ContractVerificationFailed) + } + } +} diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 6d7550d1..59a42556 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -83,14 +83,14 @@ impl ClientCommand for VerifyContract { error, } })?; - let verbosity_level = common::verbose::get(matches); let verification_url_base_path = verification_url_base_path::get(matches); + let verbosity_level = common::verbose::get(matches); casper_client::cli::verify_contract( deploy_hash, public_key, - verbosity_level, verification_url_base_path, + verbosity_level, ) .await .map(Success::from) From 4d756f62669456773c07ae6e56b2623e9e898e8c Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Mon, 8 Jan 2024 09:19:10 +0100 Subject: [PATCH 10/31] Imploved error handling --- lib/lib.rs | 32 ++++++++++++++++--------- lib/verification.rs | 58 ++++++++++++++++++++++++++++++++------------- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/lib/lib.rs b/lib/lib.rs index fe47a6b5..42d63a64 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -577,17 +577,27 @@ pub async fn verify_contract( ); } - let project_path = current_dir().expect("Error getting current directory"); - - if verbosity == Verbosity::High { - println!("Project path: {}", project_path.display()); - } - - let archive = build_archive(&project_path).expect("Cannot create project archive"); - - if verbosity == Verbosity::High { - println!("Created project archive (size: {})", archive.len()); - } + let project_path = match current_dir() { + Ok(path) => path, + Err(error) => { + eprintln!("Cannot get current directory: {}", error); + return Err(Error::ContractVerificationFailed); + } + }; + + let archive = match build_archive(&project_path) { + Ok(archive) => { + if verbosity == Verbosity::High { + println!("Created project archive (size: {})", archive.len()); + } + + archive + } + Err(error) => { + eprintln!("Cannot create project archive: {}", error); + return Err(Error::ContractVerificationFailed); + } + }; let archive_base64 = general_purpose::STANDARD.encode(&archive); diff --git a/lib/verification.rs b/lib/verification.rs index 6fe3ef5d..f059ac1e 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -14,6 +14,7 @@ use openapi::{ models::{VerificationDetails, VerificationRequest, VerificationStatus}, }; use tar::Builder as TarBuilder; +use tokio::time::{sleep, Duration}; use crate::{types::DeployHash, Error, Verbosity}; @@ -92,9 +93,14 @@ pub async fn send_verification_request( println!("Sending verfication request to {}", configuration.base_path); } - let mut verification_result = verification_post(&configuration, Some(verification_request)) - .await - .expect("Cannot send verification request"); + let mut verification_result = + match verification_post(&configuration, Some(verification_request)).await { + Ok(verification_result) => verification_result, + Err(error) => { + eprintln!("Cannot send verification request: {:?}", error); + return Err(Error::ContractVerificationFailed); + } + }; if verbosity == Verbosity::High { println!( @@ -103,7 +109,13 @@ pub async fn send_verification_request( ); } - let mut verification_status = verification_result.status.unwrap(); + let mut verification_status = match verification_result.status { + Some(verification_status) => verification_status, + None => { + eprintln!("Verification status not found"); + return Err(Error::ContractVerificationFailed); + } + }; if verbosity == Verbosity::High { println!("Waiting for verification to finish...",); @@ -112,12 +124,28 @@ pub async fn send_verification_request( while verification_status != VerificationStatus::Verified && verification_status != VerificationStatus::Failed { - verification_result = - verification_deploy_hash_status_get(&configuration, deploy_hash.to_string().as_str()) - .await - .expect("Cannot get verification status"); + verification_result = match verification_deploy_hash_status_get( + &configuration, + deploy_hash.to_string().as_str(), + ) + .await + { + Ok(verification_result) => verification_result, + Err(error) => { + eprintln!("Cannot get verification status: {:?}", error); + return Err(Error::ContractVerificationFailed); + } + }; - verification_status = verification_result.status.unwrap(); + verification_status = match verification_result.status { + Some(verification_status) => verification_status, + None => { + eprintln!("Verification status not found"); + return Err(Error::ContractVerificationFailed); + } + }; + + sleep(Duration::from_millis(100)).await; } if verbosity == Verbosity::High { @@ -131,16 +159,12 @@ pub async fn send_verification_request( println!("Getting verification details..."); } - let verification_details = - verification_deploy_hash_details_get(&configuration, deploy_hash.to_string().as_str()) - .await; - - match verification_details { + match verification_deploy_hash_details_get(&configuration, deploy_hash.to_string().as_str()) + .await + { Ok(verification_details) => Ok(verification_details), Err(error) => { - if verbosity == Verbosity::High { - println!("Cannot get verification details: {:?}", error); - } + eprintln!("Cannot get verification details: {:?}", error); Err(Error::ContractVerificationFailed) } } From 104d4cb5c45916a8b262b68a3cdfdce53bba77d7 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Mon, 8 Jan 2024 09:22:37 +0100 Subject: [PATCH 11/31] Improved logging --- lib/lib.rs | 5 ++--- lib/verification.rs | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/lib.rs b/lib/lib.rs index 42d63a64..a333e5f9 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -568,7 +568,7 @@ pub async fn verify_contract( verification_url_base_path: &str, verbosity: Verbosity, ) -> Result { - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!("Deploy hash: {}", deploy_hash.to_string()); println!("Public key: {}", public_key.to_account_hash()); println!( @@ -587,10 +587,9 @@ pub async fn verify_contract( let archive = match build_archive(&project_path) { Ok(archive) => { - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!("Created project archive (size: {})", archive.len()); } - archive } Err(error) => { diff --git a/lib/verification.rs b/lib/verification.rs index f059ac1e..97dd5314 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -89,7 +89,7 @@ pub async fn send_verification_request( let mut configuration = Configuration::default(); configuration.base_path = verification_url_base_path.to_string(); - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!("Sending verfication request to {}", configuration.base_path); } @@ -102,7 +102,7 @@ pub async fn send_verification_request( } }; - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!( "Sent verification request - status {}", verification_result.status.unwrap().to_string() @@ -117,7 +117,7 @@ pub async fn send_verification_request( } }; - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!("Waiting for verification to finish...",); } @@ -148,14 +148,14 @@ pub async fn send_verification_request( sleep(Duration::from_millis(100)).await; } - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!( "Verification finished - status {}", verification_status.to_string() ); } - if verbosity == Verbosity::High { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!("Getting verification details..."); } From f07b7ecd3f596b1a0884792aa1f942cd68099f2f Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Mon, 8 Jan 2024 09:46:10 +0100 Subject: [PATCH 12/31] Refactored verification function --- lib/verification.rs | 84 +++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/lib/verification.rs b/lib/verification.rs index 97dd5314..222546b6 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -93,7 +93,7 @@ pub async fn send_verification_request( println!("Sending verfication request to {}", configuration.base_path); } - let mut verification_result = + let verification_result = match verification_post(&configuration, Some(verification_request)).await { Ok(verification_result) => verification_result, Err(error) => { @@ -109,43 +109,49 @@ pub async fn send_verification_request( ); } - let mut verification_status = match verification_result.status { - Some(verification_status) => verification_status, - None => { - eprintln!("Verification status not found"); - return Err(Error::ContractVerificationFailed); - } - }; + wait_for_verification_finished(&configuration, deploy_hash, verbosity).await; if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Waiting for verification to finish...",); + println!("Getting verification details..."); } + match verification_deploy_hash_details_get(&configuration, deploy_hash.to_string().as_str()) + .await + { + Ok(verification_details) => Ok(verification_details), + Err(error) => { + eprintln!("Cannot get verification details: {:?}", error); + Err(Error::ContractVerificationFailed) + } + } +} + +async fn wait_for_verification_finished( + configuration: &Configuration, + deploy_hash: DeployHash, + verbosity: Verbosity, +) { + let mut verification_status = match get_verification_status(configuration, deploy_hash).await { + Ok(verification_status) => verification_status, + Err(error) => { + eprintln!("Cannot get verification status: {:?}", error); + return; + } + }; + while verification_status != VerificationStatus::Verified && verification_status != VerificationStatus::Failed { - verification_result = match verification_deploy_hash_status_get( - &configuration, - deploy_hash.to_string().as_str(), - ) - .await - { - Ok(verification_result) => verification_result, + verification_status = match get_verification_status(configuration, deploy_hash).await { + Ok(verification_status) => verification_status, Err(error) => { eprintln!("Cannot get verification status: {:?}", error); - return Err(Error::ContractVerificationFailed); - } - }; - - verification_status = match verification_result.status { - Some(verification_status) => verification_status, - None => { - eprintln!("Verification status not found"); - return Err(Error::ContractVerificationFailed); + return; } }; sleep(Duration::from_millis(100)).await; + // TODO: Add backoff with limited retries. } if verbosity == Verbosity::Medium || verbosity == Verbosity::High { @@ -154,17 +160,27 @@ pub async fn send_verification_request( verification_status.to_string() ); } +} - if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Getting verification details..."); - } +async fn get_verification_status( + configuration: &Configuration, + deploy_hash: DeployHash, +) -> Result { + let verification_status = + match verification_deploy_hash_status_get(&configuration, deploy_hash.to_string().as_str()) + .await + { + Ok(verification_result) => verification_result, + Err(error) => { + eprintln!("Failed to fetch verification status: {:?}", error); + return Err(Error::ContractVerificationFailed); + } + }; - match verification_deploy_hash_details_get(&configuration, deploy_hash.to_string().as_str()) - .await - { - Ok(verification_details) => Ok(verification_details), - Err(error) => { - eprintln!("Cannot get verification details: {:?}", error); + match verification_status.status { + Some(verification_status) => Ok(verification_status), + None => { + eprintln!("Verification status not found"); Err(Error::ContractVerificationFailed) } } From d72f852c80bf4d8b09c7502fa5da527cb0bd1804 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Mon, 8 Jan 2024 10:14:55 +0100 Subject: [PATCH 13/31] Initialized API client --- lib/verification.rs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/verification.rs b/lib/verification.rs index 222546b6..f4ce9c20 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -86,11 +86,18 @@ pub async fn send_verification_request( code_archive: Some(archive_base64), }; - let mut configuration = Configuration::default(); - configuration.base_path = verification_url_base_path.to_string(); + let configuration = Configuration { + base_path: verification_url_base_path.to_string(), + user_agent: Some("casper-client-rs".to_owned()), + client: reqwest::Client::new(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + }; if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Sending verfication request to {}", configuration.base_path); + println!("Sending verification request to {}", configuration.base_path); } let verification_result = @@ -102,11 +109,19 @@ pub async fn send_verification_request( } }; - if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!( - "Sent verification request - status {}", - verification_result.status.unwrap().to_string() - ); + match verification_result.status { + Some(verification_status) => { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { + println!( + "Sent verification request - status {}", + verification_status.to_string() + ); + } + }, + None => { + eprintln!("Verification status not found"); + return Err(Error::ContractVerificationFailed); + } } wait_for_verification_finished(&configuration, deploy_hash, verbosity).await; @@ -126,6 +141,7 @@ pub async fn send_verification_request( } } +/// Waits for the verification process to finish. async fn wait_for_verification_finished( configuration: &Configuration, deploy_hash: DeployHash, @@ -162,6 +178,7 @@ async fn wait_for_verification_finished( } } +/// Gets the verification status of the contract. async fn get_verification_status( configuration: &Configuration, deploy_hash: DeployHash, From 42664927d62a27d01bfd73d85fa11cb919975365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 22 Jan 2024 09:54:53 +0100 Subject: [PATCH 14/31] Adopt new datatypes; use reqwest --- Cargo.toml | 1 - lib/cli.rs | 19 ++--- lib/lib.rs | 29 +++---- lib/verification.rs | 158 ++++++++++++++++++++------------------ lib/verification_types.rs | 75 ++++++++++++++++++ src/account_address.rs | 1 - src/common.rs | 23 ++---- src/verify_contract.rs | 36 ++------- 8 files changed, 188 insertions(+), 154 deletions(-) create mode 100644 lib/verification_types.rs diff --git a/Cargo.toml b/Cargo.toml index 625f4153..83e1bf4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ base64 = "0.21.5" bytes = "1.5.0" casper-hashing = "3.0.0" casper-types = { version = "4.0.1", features = ["std"] } -openapi = { path = "../Casper-SCVS-Validator/openapi" } clap = { version = "4.4.10", optional = true, features = ["cargo", "deprecated", "wrap_help"] } clap_complete = { version = "4.4.4", optional = true } flate2 = "1.0.28" diff --git a/lib/cli.rs b/lib/cli.rs index 1e6985ce..84ff8231 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -33,12 +33,10 @@ mod simple_args; #[cfg(test)] mod tests; -use openapi::models::VerificationDetails; #[cfg(feature = "std-fs-io")] use serde::Serialize; use casper_hashing::Digest; -use casper_types::PublicKey; use casper_types::URef; #[cfg(doc)] use casper_types::{account::AccountHash, Key}; @@ -55,6 +53,7 @@ use crate::{ DictionaryItemIdentifier, }, types::Deploy, + verification_types::VerificationDetails, SuccessResponse, }; #[cfg(doc)] @@ -713,19 +712,13 @@ pub async fn get_era_info( /// Verifies the smart contract code againt the one deployed at address. pub async fn verify_contract( - deploy_hash: &str, - public_key: PublicKey, + hash_str: &str, verification_url_base_path: &str, verbosity_level: u64, ) -> Result { - let deploy_hash = parse::deploy_hash(deploy_hash)?; + let key = parse::key_for_query(hash_str)?; let verbosity = parse::verbosity(verbosity_level); - crate::verify_contract( - deploy_hash, - public_key, - verification_url_base_path, - verbosity, - ) - .await - .map_err(CliError::from) + crate::verify_contract(key, verification_url_base_path, verbosity) + .await + .map_err(CliError::from) } diff --git a/lib/lib.rs b/lib/lib.rs index a333e5f9..97933fb4 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -49,6 +49,7 @@ pub mod types; mod validation; mod verbosity; mod verification; +mod verification_types; use std::env::current_dir; #[cfg(feature = "std-fs-io")] @@ -58,7 +59,6 @@ use std::{ path::Path, }; -use openapi::models::VerificationDetails; #[cfg(feature = "std-fs-io")] use serde::Serialize; @@ -67,7 +67,7 @@ use casper_hashing::Digest; use casper_types::SecretKey; #[cfg(doc)] use casper_types::Transfer; -use casper_types::{Key, PublicKey, URef}; +use casper_types::{Key, URef}; pub use error::Error; use json_rpc::JsonRpcCall; @@ -115,8 +115,9 @@ use types::{Deploy, DeployHash}; pub use validation::ValidateResponseError; pub use verbosity::Verbosity; pub use verification::{build_archive, send_verification_request}; +use verification_types::VerificationDetails; -use base64::{engine::general_purpose, Engine as _}; +use base64::{engine::general_purpose::STANDARD, Engine}; /// Puts a [`Deploy`] to the network for execution. /// @@ -563,18 +564,13 @@ pub async fn get_era_info( /// Verifies the smart contract code againt the one deployed at deploy hash. pub async fn verify_contract( - deploy_hash: DeployHash, - public_key: PublicKey, + key: Key, verification_url_base_path: &str, verbosity: Verbosity, ) -> Result { if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Deploy hash: {}", deploy_hash.to_string()); - println!("Public key: {}", public_key.to_account_hash()); - println!( - "Verification service base path: {}", - verification_url_base_path - ); + println!("Key: {key}"); + println!("Verification service base path: {verification_url_base_path}",); } let project_path = match current_dir() { @@ -598,14 +594,7 @@ pub async fn verify_contract( } }; - let archive_base64 = general_purpose::STANDARD.encode(&archive); + let archive_base64 = STANDARD.encode(&archive); - send_verification_request( - deploy_hash, - public_key, - verification_url_base_path, - archive_base64, - verbosity, - ) - .await + send_verification_request(key, verification_url_base_path, archive_base64, verbosity).await } diff --git a/lib/verification.rs b/lib/verification.rs index f4ce9c20..358ec839 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -1,22 +1,19 @@ use std::path::Path; use bytes::{BufMut, Bytes, BytesMut}; -use casper_types::PublicKey; +use casper_types::Key; use flate2::{write::GzEncoder, Compression}; -use openapi::{ - apis::{ - configuration::Configuration, - default_api::{ - verification_deploy_hash_details_get, verification_deploy_hash_status_get, - verification_post, - }, - }, - models::{VerificationDetails, VerificationRequest, VerificationStatus}, +use reqwest::{ + header::{HeaderMap, HeaderValue, CONTENT_TYPE}, + Client, ClientBuilder, StatusCode, }; use tar::Builder as TarBuilder; use tokio::time::{sleep, Duration}; -use crate::{types::DeployHash, Error, Verbosity}; +use crate::{ + verification_types::{VerificationDetails, VerificationRequest, VerificationStatus}, + Error, Verbosity, +}; static GIT_DIR_NAME: &str = ".git"; static TARGET_DIR_NAME: &str = "target"; @@ -67,75 +64,81 @@ pub fn build_archive(path: &Path) -> Result { /// /// * `deploy_hash` - The hash of the deployed contract. /// * `public_key` - The public key associated with the contract. -/// * `verification_url_base_path` - The base path of the verification URL. +/// * `base_url` - The base path of the verification URL. /// * `verbosity` - The verbosity level of the verification process. /// /// # Returns /// /// The verification details of the contract. pub async fn send_verification_request( - deploy_hash: DeployHash, - public_key: PublicKey, - verification_url_base_path: &str, - archive_base64: String, + key: Key, + base_url: &str, + code_archive: String, verbosity: Verbosity, ) -> Result { let verification_request = VerificationRequest { - deploy_hash: Some(deploy_hash.to_string()), - public_key: Some(public_key.to_account_hash().to_string()), - code_archive: Some(archive_base64), + deploy_hash: key, + code_archive, }; - let configuration = Configuration { - base_path: verification_url_base_path.to_string(), - user_agent: Some("casper-client-rs".to_owned()), - client: reqwest::Client::new(), - basic_auth: None, - oauth_access_token: None, - bearer_access_token: None, - api_key: None, + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let Ok(http_client) = ClientBuilder::new() + .default_headers(headers) + .user_agent("casper-client-rs") + .build() + else { + eprintln!("Failed to build HTTP client"); + return Err(Error::ContractVerificationFailed); // FIXME: different error }; if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Sending verification request to {}", configuration.base_path); + println!("Sending verification request to {base_url}",); } - let verification_result = - match verification_post(&configuration, Some(verification_request)).await { - Ok(verification_result) => verification_result, - Err(error) => { - eprintln!("Cannot send verification request: {:?}", error); - return Err(Error::ContractVerificationFailed); - } - }; + let response = match http_client + .post(base_url) + .json(&verification_request) + .send() + .await + { + Ok(response) => response, + Err(error) => { + eprintln!("Cannot send verification request: {error:?}"); + return Err(Error::ContractVerificationFailed); + } + }; - match verification_result.status { - Some(verification_status) => { + match response.status() { + StatusCode::OK => { if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!( - "Sent verification request - status {}", - verification_status.to_string() - ); - } - }, - None => { - eprintln!("Verification status not found"); + println!("Sent verification request",); + } + } + status => { + eprintln!("Verification faile with status {status}"); return Err(Error::ContractVerificationFailed); } } - wait_for_verification_finished(&configuration, deploy_hash, verbosity).await; + wait_for_verification_finished(base_url, &http_client, key, verbosity).await; if verbosity == Verbosity::Medium || verbosity == Verbosity::High { println!("Getting verification details..."); } - match verification_deploy_hash_details_get(&configuration, deploy_hash.to_string().as_str()) + match http_client + .get(base_url.to_string() + "/" + &key.to_formatted_string() + "/details") + .send() .await { - Ok(verification_details) => Ok(verification_details), + Ok(response) => response.json().await.map_err(|err| { + eprintln!("Failed to parse JSON {err}"); + Error::ContractVerificationFailed + }), Err(error) => { - eprintln!("Cannot get verification details: {:?}", error); + eprintln!("Cannot get verification details: {error:?}"); Err(Error::ContractVerificationFailed) } } @@ -143,14 +146,15 @@ pub async fn send_verification_request( /// Waits for the verification process to finish. async fn wait_for_verification_finished( - configuration: &Configuration, - deploy_hash: DeployHash, + base_url: &str, + http_client: &Client, + key: Key, verbosity: Verbosity, ) { - let mut verification_status = match get_verification_status(configuration, deploy_hash).await { + let mut verification_status = match get_verification_status(base_url, http_client, key).await { Ok(verification_status) => verification_status, Err(error) => { - eprintln!("Cannot get verification status: {:?}", error); + eprintln!("Cannot get verification status: {error:?}"); return; } }; @@ -158,10 +162,10 @@ async fn wait_for_verification_finished( while verification_status != VerificationStatus::Verified && verification_status != VerificationStatus::Failed { - verification_status = match get_verification_status(configuration, deploy_hash).await { + verification_status = match get_verification_status(base_url, http_client, key).await { Ok(verification_status) => verification_status, Err(error) => { - eprintln!("Cannot get verification status: {:?}", error); + eprintln!("Cannot get verification status: {error:?}"); return; } }; @@ -171,33 +175,35 @@ async fn wait_for_verification_finished( } if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!( - "Verification finished - status {}", - verification_status.to_string() - ); + println!("Verification finished - status {verification_status:?}"); } } /// Gets the verification status of the contract. async fn get_verification_status( - configuration: &Configuration, - deploy_hash: DeployHash, + base_url: &str, + http_client: &Client, + key: Key, ) -> Result { - let verification_status = - match verification_deploy_hash_status_get(&configuration, deploy_hash.to_string().as_str()) - .await - { - Ok(verification_result) => verification_result, - Err(error) => { - eprintln!("Failed to fetch verification status: {:?}", error); - return Err(Error::ContractVerificationFailed); - } - }; + let response = match http_client + .get(base_url.to_string() + "/" + &key.to_formatted_string() + "/status") + .send() + .await + { + Ok(response) => response, + Err(error) => { + eprintln!("Failed to fetch verification status: {error:?}"); + return Err(Error::ContractVerificationFailed); + } + }; - match verification_status.status { - Some(verification_status) => Ok(verification_status), - None => { - eprintln!("Verification status not found"); + match response.status() { + StatusCode::OK => response.json().await.map_err(|err| { + eprintln!("Failed to parse JSON for verification status, {err}"); + Error::ContractVerificationFailed + }), + status => { + eprintln!("Verification status not found, {status}"); Err(Error::ContractVerificationFailed) } } diff --git a/lib/verification_types.rs b/lib/verification_types.rs new file mode 100644 index 00000000..9348db3a --- /dev/null +++ b/lib/verification_types.rs @@ -0,0 +1,75 @@ +use casper_types::Key; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub(crate) enum VerificationStatus { + Failed, + Pending, + Verified, + Waiting, +} + +// Any update to this enum should be reflected in migrations. +#[derive(Deserialize, Serialize)] +#[non_exhaustive] +pub(crate) enum VerificationErrorCode { + Ok, + None, + BytecodeMismatch, + BuildError, + ContractAlreadyVerified, + ContractNotFound, + Internal, + InvalidContract, + InvalidHash, + WrongCodeArchive, +} + +#[derive(Serialize)] +pub(crate) struct VerificationError { + pub(crate) code: VerificationErrorCode, + pub(crate) description: String, +} + +#[derive(Serialize)] +pub(crate) struct VerificationRequest { + // Deploy hash of the contract deployment transaction. + pub(crate) deploy_hash: Key, + // Base64 encoded tar archive containing contract source code. + pub(crate) code_archive: String, +} + +#[derive(Serialize)] +pub(crate) struct VerificationResult { + pub(crate) status: VerificationStatus, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) error: Option, +} + +#[derive(Deserialize, Serialize)] +pub(crate) struct VerificationDetailsResult { + // pub(crate) toolchain: Toolchain, + pub(crate) modules: Vec, + pub(crate) binary_uri: String, + pub(crate) logs_uri: String, +} + +#[derive(Deserialize, Serialize)] +pub struct VerificationDetails { + pub(crate) status: VerificationStatus, + pub(crate) result: VerificationDetailsResult, +} + +// #[derive(Serialize)] +// pub(crate) struct Toolchain { +// pub(crate) channel: String, +// pub(crate) components: Vec, +// pub(crate) targets: Vec, +// pub(crate) profile: String, +// } + +#[derive(Deserialize, Serialize)] +pub(crate) struct Module { + pub(crate) name: String, + pub(crate) source_code_uri: String, +} diff --git a/src/account_address.rs b/src/account_address.rs index ed68531d..e221a0a0 100644 --- a/src/account_address.rs +++ b/src/account_address.rs @@ -29,7 +29,6 @@ impl ClientCommand for AccountAddress { .arg(common::public_key::arg( DisplayOrder::PublicKey as usize, PUBLIC_KEY_IS_REQUIRED, - false, )) } diff --git a/src/common.rs b/src/common.rs index 718e76d8..2379388d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -226,21 +226,14 @@ pub(super) mod public_key { should be one of the two public key files generated via the `keygen` subcommand; \ \"public_key_hex\" or \"public_key.pem\""; - pub fn arg(order: usize, is_required: bool, is_positional: bool) -> Arg { - match is_positional { - true => Arg::new(ARG_NAME) - .required(true) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) - .display_order(order), - false => Arg::new(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(is_required) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) - .display_order(order), - } + pub fn arg(order: usize, is_required: bool) -> Arg { + Arg::new(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(is_required) + .value_name(ARG_VALUE_NAME) + .help(ARG_HELP) + .display_order(order) } pub fn get(matches: &ArgMatches, is_required: bool) -> Result { diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 59a42556..84a3be25 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -1,16 +1,10 @@ use std::str; use async_trait::async_trait; -use casper_types::{AsymmetricType, PublicKey}; -use clap::{ArgMatches, Command}; - use casper_client::cli::CliError; +use clap::{ArgMatches, Command}; -use crate::{ - command::ClientCommand, - common::{self}, - Success, -}; +use crate::{command::ClientCommand, common, Success}; use clap::Arg; @@ -27,20 +21,16 @@ enum DisplayOrder { pub mod verification_url_base_path { use super::*; - const ARG_NAME: &str = "verification-url-basepath"; - const ARG_SHORT: char = 'u'; - const ARG_VALUE_NAME: &str = "HOST:PORT"; - const ARG_DEFAULT: &str = "http://localhost:8080"; - const ARG_HELP: &str = "Hostname or IP and port of the verification API"; + static ARG_NAME: &str = "verification-url-basepath"; pub fn arg(order: usize) -> Arg { Arg::new(ARG_NAME) .long(ARG_NAME) - .short(ARG_SHORT) + .short('u') .required(false) - .default_value(ARG_DEFAULT) - .value_name(ARG_VALUE_NAME) - .help(ARG_HELP) + .default_value("http://localhost:8080") + .value_name("HOST:PORT") + .help("Hostname or IP and port of the verification API") .display_order(order) } @@ -48,7 +38,7 @@ pub mod verification_url_base_path { matches .get_one::(ARG_NAME) .map(String::as_str) - .unwrap_or_else(|| panic!("should have {} arg", ARG_NAME)) + .unwrap_or_else(|| panic!("should have {ARG_NAME} arg")) } } @@ -66,7 +56,6 @@ impl ClientCommand for VerifyContract { .arg(common::public_key::arg( DisplayOrder::PublicKey as usize, true, - true, )) .arg(verification_url_base_path::arg( DisplayOrder::VerificationUrlBasePath as usize, @@ -75,20 +64,11 @@ impl ClientCommand for VerifyContract { async fn run(matches: &ArgMatches) -> Result { let deploy_hash = common::deploy_hash::get(matches); - let hex_public_key = common::public_key::get(matches, true)?; - let public_key = PublicKey::from_hex(&hex_public_key).map_err(|error| { - eprintln!("Can't parse {} as a public key: {}", hex_public_key, error); - CliError::FailedToParsePublicKey { - context: "account-address".to_string(), - error, - } - })?; let verification_url_base_path = verification_url_base_path::get(matches); let verbosity_level = common::verbose::get(matches); casper_client::cli::verify_contract( deploy_hash, - public_key, verification_url_base_path, verbosity_level, ) From 979ed97c83bf1b28be1327f1a501563964068fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 22 Jan 2024 13:43:17 +0100 Subject: [PATCH 15/31] Cleanup --- lib/lib.rs | 14 +++++++++----- lib/verification.rs | 19 +++++++------------ src/verify_contract.rs | 5 ----- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/lib/lib.rs b/lib/lib.rs index 97933fb4..2b4a3e04 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -576,7 +576,7 @@ pub async fn verify_contract( let project_path = match current_dir() { Ok(path) => path, Err(error) => { - eprintln!("Cannot get current directory: {}", error); + eprintln!("Cannot get current directory: {error}"); return Err(Error::ContractVerificationFailed); } }; @@ -589,12 +589,16 @@ pub async fn verify_contract( archive } Err(error) => { - eprintln!("Cannot create project archive: {}", error); + eprintln!("Cannot create project archive: {error}"); return Err(Error::ContractVerificationFailed); } }; - let archive_base64 = STANDARD.encode(&archive); - - send_verification_request(key, verification_url_base_path, archive_base64, verbosity).await + send_verification_request( + key, + verification_url_base_path, + STANDARD.encode(&archive), + verbosity, + ) + .await } diff --git a/lib/verification.rs b/lib/verification.rs index 358ec839..ca60dd71 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -94,11 +94,12 @@ pub async fn send_verification_request( }; if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Sending verification request to {base_url}",); + println!("Sending verification request"); } + let url = base_url.to_string() + "/verification"; let response = match http_client - .post(base_url) + .post(url) .json(&verification_request) .send() .await @@ -128,11 +129,8 @@ pub async fn send_verification_request( println!("Getting verification details..."); } - match http_client - .get(base_url.to_string() + "/" + &key.to_formatted_string() + "/details") - .send() - .await - { + let url = base_url.to_string() + "/verification" + &key.to_formatted_string() + "/details"; + match http_client.get(url).send().await { Ok(response) => response.json().await.map_err(|err| { eprintln!("Failed to parse JSON {err}"); Error::ContractVerificationFailed @@ -185,11 +183,8 @@ async fn get_verification_status( http_client: &Client, key: Key, ) -> Result { - let response = match http_client - .get(base_url.to_string() + "/" + &key.to_formatted_string() + "/status") - .send() - .await - { + let url = base_url.to_string() + "/verification" + &key.to_formatted_string() + "/status"; + let response = match http_client.get(url).send().await { Ok(response) => response, Err(error) => { eprintln!("Failed to fetch verification status: {error:?}"); diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 84a3be25..c671be97 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -14,7 +14,6 @@ pub struct VerifyContract; enum DisplayOrder { Verbose, DeployHash, - PublicKey, VerificationUrlBasePath, } @@ -53,10 +52,6 @@ impl ClientCommand for VerifyContract { .display_order(display_order) .arg(common::verbose::arg(DisplayOrder::Verbose as usize)) .arg(common::deploy_hash::arg(DisplayOrder::DeployHash as usize)) - .arg(common::public_key::arg( - DisplayOrder::PublicKey as usize, - true, - )) .arg(verification_url_base_path::arg( DisplayOrder::VerificationUrlBasePath as usize, )) From 239210479a77379922f3fce81cebd9c5f8970652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 22 Jan 2024 13:57:37 +0100 Subject: [PATCH 16/31] Fix result type --- lib/verification.rs | 17 +++++++++++------ lib/verification_types.rs | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/verification.rs b/lib/verification.rs index ca60dd71..183688c9 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -11,7 +11,9 @@ use tar::Builder as TarBuilder; use tokio::time::{sleep, Duration}; use crate::{ - verification_types::{VerificationDetails, VerificationRequest, VerificationStatus}, + verification_types::{ + VerificationDetails, VerificationRequest, VerificationResult, VerificationStatus, + }, Error, Verbosity, }; @@ -118,7 +120,7 @@ pub async fn send_verification_request( } } status => { - eprintln!("Verification faile with status {status}"); + eprintln!("Verification failed with status {status}"); return Err(Error::ContractVerificationFailed); } } @@ -193,10 +195,13 @@ async fn get_verification_status( }; match response.status() { - StatusCode::OK => response.json().await.map_err(|err| { - eprintln!("Failed to parse JSON for verification status, {err}"); - Error::ContractVerificationFailed - }), + StatusCode::OK => { + let result: VerificationResult = response.json().await.map_err(|err| { + eprintln!("Failed to parse JSON for verification status, {err}"); + Error::ContractVerificationFailed + })?; + Ok(result.status) + } status => { eprintln!("Verification status not found, {status}"); Err(Error::ContractVerificationFailed) diff --git a/lib/verification_types.rs b/lib/verification_types.rs index 9348db3a..3c918f45 100644 --- a/lib/verification_types.rs +++ b/lib/verification_types.rs @@ -25,7 +25,7 @@ pub(crate) enum VerificationErrorCode { WrongCodeArchive, } -#[derive(Serialize)] +#[derive(Deserialize, Serialize)] pub(crate) struct VerificationError { pub(crate) code: VerificationErrorCode, pub(crate) description: String, @@ -39,7 +39,7 @@ pub(crate) struct VerificationRequest { pub(crate) code_archive: String, } -#[derive(Serialize)] +#[derive(Deserialize, Serialize)] pub(crate) struct VerificationResult { pub(crate) status: VerificationStatus, #[serde(skip_serializing_if = "Option::is_none")] From 4f321b5b501cde0338dee093fc67d096396d9cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 22 Jan 2024 19:34:21 +0100 Subject: [PATCH 17/31] Fixes and cleanups --- lib/verification.rs | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/verification.rs b/lib/verification.rs index 183688c9..1dba9e34 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -88,6 +88,8 @@ pub async fn send_verification_request( let Ok(http_client) = ClientBuilder::new() .default_headers(headers) + // https://github.com/hyperium/hyper/issues/2136 + .pool_max_idle_per_host(0) .user_agent("casper-client-rs") .build() else { @@ -121,7 +123,7 @@ pub async fn send_verification_request( } status => { eprintln!("Verification failed with status {status}"); - return Err(Error::ContractVerificationFailed); + // return Err(Error::ContractVerificationFailed); } } @@ -131,7 +133,7 @@ pub async fn send_verification_request( println!("Getting verification details..."); } - let url = base_url.to_string() + "/verification" + &key.to_formatted_string() + "/details"; + let url = base_url.to_string() + "/verification/" + &key.to_formatted_string() + "/details"; match http_client.get(url).send().await { Ok(response) => response.json().await.map_err(|err| { eprintln!("Failed to parse JSON {err}"); @@ -151,31 +153,28 @@ async fn wait_for_verification_finished( key: Key, verbosity: Verbosity, ) { - let mut verification_status = match get_verification_status(base_url, http_client, key).await { - Ok(verification_status) => verification_status, - Err(error) => { - eprintln!("Cannot get verification status: {error:?}"); - return; - } - }; - - while verification_status != VerificationStatus::Verified - && verification_status != VerificationStatus::Failed - { - verification_status = match get_verification_status(base_url, http_client, key).await { - Ok(verification_status) => verification_status, + let delay = Duration::from_secs(1); + let mut retries = 30; + while retries != 0 { + sleep(delay).await; + // TODO: Back off with limited retries. + + match get_verification_status(base_url, http_client, key).await { + Ok(status) => { + if verbosity == Verbosity::Medium || verbosity == Verbosity::High { + println!("Verification finished with status: {status:?}"); + } + if status == VerificationStatus::Verified || status == VerificationStatus::Failed { + break; + } + } Err(error) => { eprintln!("Cannot get verification status: {error:?}"); - return; + break; } }; - sleep(Duration::from_millis(100)).await; - // TODO: Add backoff with limited retries. - } - - if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Verification finished - status {verification_status:?}"); + retries -= 1; } } @@ -185,7 +184,7 @@ async fn get_verification_status( http_client: &Client, key: Key, ) -> Result { - let url = base_url.to_string() + "/verification" + &key.to_formatted_string() + "/status"; + let url = base_url.to_string() + "/verification/" + &key.to_formatted_string() + "/status"; let response = match http_client.get(url).send().await { Ok(response) => response, Err(error) => { From 21012c45a61a393690b67f4549a47648e04843eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Tue, 23 Jan 2024 11:58:44 +0100 Subject: [PATCH 18/31] Sync structs with verificator --- lib/verification_types.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/verification_types.rs b/lib/verification_types.rs index 3c918f45..e584fd4d 100644 --- a/lib/verification_types.rs +++ b/lib/verification_types.rs @@ -49,7 +49,6 @@ pub(crate) struct VerificationResult { #[derive(Deserialize, Serialize)] pub(crate) struct VerificationDetailsResult { // pub(crate) toolchain: Toolchain, - pub(crate) modules: Vec, pub(crate) binary_uri: String, pub(crate) logs_uri: String, } @@ -67,9 +66,3 @@ pub struct VerificationDetails { // pub(crate) targets: Vec, // pub(crate) profile: String, // } - -#[derive(Deserialize, Serialize)] -pub(crate) struct Module { - pub(crate) name: String, - pub(crate) source_code_uri: String, -} From 5e43e05352af5d1e516cf1440372a1a6e7d01fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Wed, 24 Jan 2024 11:39:42 +0100 Subject: [PATCH 19/31] More retries --- lib/verification.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/verification.rs b/lib/verification.rs index 1dba9e34..1f8998ff 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -154,10 +154,9 @@ async fn wait_for_verification_finished( verbosity: Verbosity, ) { let delay = Duration::from_secs(1); - let mut retries = 30; + let mut retries = 10_000; while retries != 0 { sleep(delay).await; - // TODO: Back off with limited retries. match get_verification_status(base_url, http_client, key).await { Ok(status) => { From 5a3af7ca5b33e3a85d2e55365a6f38d3448677ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 25 Jan 2024 11:01:07 +0100 Subject: [PATCH 20/31] Increase number of retries --- lib/verification.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/verification.rs b/lib/verification.rs index 1f8998ff..48c31d1f 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -153,8 +153,8 @@ async fn wait_for_verification_finished( key: Key, verbosity: Verbosity, ) { - let delay = Duration::from_secs(1); - let mut retries = 10_000; + let delay = Duration::from_secs(2); + let mut retries = 20_000; while retries != 0 { sleep(delay).await; From af1c39b3befb1906011fdfbcc53bede0e7e2ac14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 25 Jan 2024 17:22:33 +0100 Subject: [PATCH 21/31] Shorten message --- lib/verification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/verification.rs b/lib/verification.rs index 48c31d1f..ab766692 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -161,7 +161,7 @@ async fn wait_for_verification_finished( match get_verification_status(base_url, http_client, key).await { Ok(status) => { if verbosity == Verbosity::Medium || verbosity == Verbosity::High { - println!("Verification finished with status: {status:?}"); + println!("Verification status: {status:?}"); } if status == VerificationStatus::Verified || status == VerificationStatus::Failed { break; From d9caa433f7647d62aab474d2f21e7261a3f31f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Wed, 10 Apr 2024 12:39:19 +0200 Subject: [PATCH 22/31] Resolve comments --- lib/cli.rs | 12 +++++++++--- lib/error.rs | 4 ++++ lib/lib.rs | 16 ++++++++++------ lib/verification.rs | 13 +++++++------ lib/verification_types.rs | 8 -------- src/verify_contract.rs | 34 +++++++++++++++++++++++++++++----- 6 files changed, 59 insertions(+), 28 deletions(-) diff --git a/lib/cli.rs b/lib/cli.rs index 84ff8231..2c72af95 100644 --- a/lib/cli.rs +++ b/lib/cli.rs @@ -714,11 +714,17 @@ pub async fn get_era_info( pub async fn verify_contract( hash_str: &str, verification_url_base_path: &str, + verification_project_path: Option<&str>, verbosity_level: u64, ) -> Result { let key = parse::key_for_query(hash_str)?; let verbosity = parse::verbosity(verbosity_level); - crate::verify_contract(key, verification_url_base_path, verbosity) - .await - .map_err(CliError::from) + crate::verify_contract( + key, + verification_url_base_path, + verification_project_path, + verbosity, + ) + .await + .map_err(CliError::from) } diff --git a/lib/error.rs b/lib/error.rs index 4e8ff8d6..875b0c67 100644 --- a/lib/error.rs +++ b/lib/error.rs @@ -176,6 +176,10 @@ pub enum Error { /// Failed to verify contract. #[error("contract verification failed")] ContractVerificationFailed, + + /// Failed to construct HTTP client. + #[error("failed to construct HTTP client")] + FailedToConstructHttpClient, } impl From for Error { diff --git a/lib/lib.rs b/lib/lib.rs index 2b4a3e04..8829c2cf 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -566,6 +566,7 @@ pub async fn get_era_info( pub async fn verify_contract( key: Key, verification_url_base_path: &str, + project_path: Option<&str>, verbosity: Verbosity, ) -> Result { if verbosity == Verbosity::Medium || verbosity == Verbosity::High { @@ -573,12 +574,15 @@ pub async fn verify_contract( println!("Verification service base path: {verification_url_base_path}",); } - let project_path = match current_dir() { - Ok(path) => path, - Err(error) => { - eprintln!("Cannot get current directory: {error}"); - return Err(Error::ContractVerificationFailed); - } + let project_path = match project_path { + Some(path) => Path::new(path).to_path_buf(), + None => match current_dir() { + Ok(path) => path, + Err(error) => { + eprintln!("Cannot get current directory: {error}"); + return Err(Error::ContractVerificationFailed); + } + }, }; let archive = match build_archive(&project_path) { diff --git a/lib/verification.rs b/lib/verification.rs index ab766692..b5272fd2 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -17,6 +17,9 @@ use crate::{ Error, Verbosity, }; +const MAX_RETRIES: usize = 1200; +const RETRY_DELAY: Duration = Duration::from_secs(3); + static GIT_DIR_NAME: &str = ".git"; static TARGET_DIR_NAME: &str = "target"; @@ -38,7 +41,7 @@ pub fn build_archive(path: &Path) -> Result { let encoder = GzEncoder::new(buffer, Compression::best()); let mut archive = TarBuilder::new(encoder); - for entry in (path.read_dir()?).flatten() { + for entry in path.read_dir()?.flatten() { let file_name = entry.file_name(); // Skip `.git` and `target`. if file_name == TARGET_DIR_NAME || file_name == GIT_DIR_NAME { @@ -94,7 +97,7 @@ pub async fn send_verification_request( .build() else { eprintln!("Failed to build HTTP client"); - return Err(Error::ContractVerificationFailed); // FIXME: different error + return Err(Error::FailedToConstructHttpClient); }; if verbosity == Verbosity::Medium || verbosity == Verbosity::High { @@ -123,7 +126,6 @@ pub async fn send_verification_request( } status => { eprintln!("Verification failed with status {status}"); - // return Err(Error::ContractVerificationFailed); } } @@ -153,10 +155,9 @@ async fn wait_for_verification_finished( key: Key, verbosity: Verbosity, ) { - let delay = Duration::from_secs(2); - let mut retries = 20_000; + let mut retries = MAX_RETRIES; while retries != 0 { - sleep(delay).await; + sleep(RETRY_DELAY).await; match get_verification_status(base_url, http_client, key).await { Ok(status) => { diff --git a/lib/verification_types.rs b/lib/verification_types.rs index e584fd4d..91b14223 100644 --- a/lib/verification_types.rs +++ b/lib/verification_types.rs @@ -58,11 +58,3 @@ pub struct VerificationDetails { pub(crate) status: VerificationStatus, pub(crate) result: VerificationDetailsResult, } - -// #[derive(Serialize)] -// pub(crate) struct Toolchain { -// pub(crate) channel: String, -// pub(crate) components: Vec, -// pub(crate) targets: Vec, -// pub(crate) profile: String, -// } diff --git a/src/verify_contract.rs b/src/verify_contract.rs index c671be97..180ad735 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -6,8 +6,6 @@ use clap::{ArgMatches, Command}; use crate::{command::ClientCommand, common, Success}; -use clap::Arg; - pub struct VerifyContract; /// This struct defines the order in which the args are shown for this subcommand's help message. @@ -15,10 +13,11 @@ enum DisplayOrder { Verbose, DeployHash, VerificationUrlBasePath, + VerificationProjectPath, } -pub mod verification_url_base_path { - use super::*; +mod verification_url_base_path { + use clap::{Arg, ArgMatches}; static ARG_NAME: &str = "verification-url-basepath"; @@ -41,10 +40,30 @@ pub mod verification_url_base_path { } } +mod verification_project_path { + use clap::{Arg, ArgMatches}; + + static ARG_NAME: &str = "verification-source-code-path"; + + pub fn arg(order: usize) -> Arg { + Arg::new(ARG_NAME) + .long(ARG_NAME) + .short('p') + .required(false) + .value_name("PATH") + .help("Source code path") + .display_order(order) + } + + pub fn get(matches: &ArgMatches) -> Option<&str> { + matches.get_one::(ARG_NAME).map(String::as_str) + } +} + #[async_trait] impl ClientCommand for VerifyContract { const NAME: &'static str = "verify-contract"; - const ABOUT: &'static str = "Verify smart contract source code"; + const ABOUT: &'static str = "Verify smart contract source code using verification service; the source code will be upload, built and compared against the deployed contract binary"; fn build(display_order: usize) -> Command { Command::new(Self::NAME) @@ -55,16 +74,21 @@ impl ClientCommand for VerifyContract { .arg(verification_url_base_path::arg( DisplayOrder::VerificationUrlBasePath as usize, )) + .arg(verification_project_path::arg( + DisplayOrder::VerificationProjectPath as usize, + )) } async fn run(matches: &ArgMatches) -> Result { let deploy_hash = common::deploy_hash::get(matches); let verification_url_base_path = verification_url_base_path::get(matches); + let verification_project_path = verification_project_path::get(matches); let verbosity_level = common::verbose::get(matches); casper_client::cli::verify_contract( deploy_hash, verification_url_base_path, + verification_project_path, verbosity_level, ) .await From 3f944b1d1eafe5b492c15b3d0509f0d1ff6f7952 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 11 Apr 2024 20:11:20 +0200 Subject: [PATCH 23/31] Update src/verify_contract.rs Co-authored-by: Zach Showalter --- src/verify_contract.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 180ad735..194a0208 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -63,7 +63,10 @@ mod verification_project_path { #[async_trait] impl ClientCommand for VerifyContract { const NAME: &'static str = "verify-contract"; - const ABOUT: &'static str = "Verify smart contract source code using verification service; the source code will be upload, built and compared against the deployed contract binary"; + const ABOUT: &'static str = "Verifies a smart contracts source code using verification service. + The source code will be uploaded, built, and compared against the deployed contract binary. + You may specify a path from which the code will be read and compressed from, or omit the path. + If the path is omitted, the archive will be built from the current working directory."; fn build(display_order: usize) -> Command { Command::new(Self::NAME) From ad4bf50ad7858efd876f4619cb03fa1fc407b2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 11 Apr 2024 20:13:58 +0200 Subject: [PATCH 24/31] cargo fmt --- src/verify_contract.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/verify_contract.rs b/src/verify_contract.rs index 194a0208..afbe7611 100644 --- a/src/verify_contract.rs +++ b/src/verify_contract.rs @@ -63,10 +63,11 @@ mod verification_project_path { #[async_trait] impl ClientCommand for VerifyContract { const NAME: &'static str = "verify-contract"; - const ABOUT: &'static str = "Verifies a smart contracts source code using verification service. - The source code will be uploaded, built, and compared against the deployed contract binary. - You may specify a path from which the code will be read and compressed from, or omit the path. - If the path is omitted, the archive will be built from the current working directory."; + const ABOUT: &'static str = + "Verifies a smart contracts source code using verification service. \ + The source code will be uploaded, built, and compared against the deployed contract binary. \ + You may specify a path from which the code will be read and compressed from, or omit the path. \ + If the path is omitted, the archive will be built from the current working directory."; fn build(display_order: usize) -> Command { Command::new(Self::NAME) From e395eae6b5496d1b453bc42fc14d330c04f9b049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 19 Apr 2024 10:45:57 +0200 Subject: [PATCH 25/31] Update vergen to v8. This resolves audit error. --- Cargo.toml | 2 +- build.rs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 83e1bf4e..11c76bcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ uint = "0.9.5" tempfile = "3.8.1" [build-dependencies] -vergen = { version = "7", default-features = false, features = ["git"] } +vergen = { version = "8", default-features = false, features = ["git", "git2"] } [patch.crates-io] casper-hashing = { git = "https://github.com/casper-network/casper-node", branch = "dev" } diff --git a/build.rs b/build.rs deleted file mode 100644 index c8dd712c..00000000 --- a/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -use vergen::{Config, ShaKind}; - -fn main() { - let mut config = Config::default(); - *config.git_mut().sha_kind_mut() = ShaKind::Short; - let _ = vergen::vergen(config); -} From 537347dcef4a04ccdfb7218764a19b269740044a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 20 Jun 2024 10:33:40 +0200 Subject: [PATCH 26/31] Resolve pull request comments --- Cargo.toml | 6 +++--- lib/verification.rs | 18 +++++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a598e0e..57f13df4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,8 @@ std-fs-io = ["casper-types/std-fs-io"] [dependencies] async-trait = { version = "0.1.74", optional = true } base16 = "0.2.1" -base64 = "0.21.5" -bytes = "1.5.0" +base64 = { version = "0.22.1", default-features = false } +bytes = { version = "1.6.0", default-features = false } casper-hashing = "3.0.0" casper-types = { version = "4.0.1", features = ["std"] } clap = { version = "~4.4", optional = true, features = ["cargo", "deprecated", "wrap_help"] } @@ -46,7 +46,7 @@ reqwest = { version = "0.12.3", features = ["json"] } schemars = "=0.8.5" serde = { version = "1.0.193", default-features = false, features = ["derive"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } -tar = "0.4.40" +tar = { version = "0.4.41", default-features = false } thiserror = "1.0.50" tokio = { version = "1.34.0", optional = true, features = ["macros", "rt", "sync", "time"] } uint = "0.9.5" diff --git a/lib/verification.rs b/lib/verification.rs index b5272fd2..8f8a8617 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::{cmp::min, io, path::Path}; use bytes::{BufMut, Bytes, BytesMut}; use casper_types::Key; @@ -17,8 +17,9 @@ use crate::{ Error, Verbosity, }; -const MAX_RETRIES: usize = 1200; -const RETRY_DELAY: Duration = Duration::from_secs(3); +const MAX_RETRIES: u32 = 10; +const BASE_DELAY: Duration = Duration::from_secs(3); +const MAX_DELAY: Duration = Duration::from_secs(300); static GIT_DIR_NAME: &str = ".git"; static TARGET_DIR_NAME: &str = "target"; @@ -36,7 +37,7 @@ static TARGET_DIR_NAME: &str = "target"; /// /// The compressed tar archive as a `Bytes` object, or an `std::io::Error` if an error occurs during /// the archiving process. -pub fn build_archive(path: &Path) -> Result { +pub fn build_archive(path: &Path) -> Result { let buffer = BytesMut::new().writer(); let encoder = GzEncoder::new(buffer, Compression::best()); let mut archive = TarBuilder::new(encoder); @@ -67,9 +68,9 @@ pub fn build_archive(path: &Path) -> Result { /// /// # Arguments /// -/// * `deploy_hash` - The hash of the deployed contract. -/// * `public_key` - The public key associated with the contract. +/// * `key` - The key of the deployed contract. /// * `base_url` - The base path of the verification URL. +/// * `code_archive` - Base64-encoded tar-gzipped archive of the source code. /// * `verbosity` - The verbosity level of the verification process. /// /// # Returns @@ -156,8 +157,10 @@ async fn wait_for_verification_finished( verbosity: Verbosity, ) { let mut retries = MAX_RETRIES; + let mut delay = BASE_DELAY; + while retries != 0 { - sleep(RETRY_DELAY).await; + sleep(delay).await; match get_verification_status(base_url, http_client, key).await { Ok(status) => { @@ -175,6 +178,7 @@ async fn wait_for_verification_finished( }; retries -= 1; + delay = min(delay * 2, MAX_DELAY); } } From 96c3013693e11e2780bc415d4ec45ae770adea94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 24 Jun 2024 09:51:11 +0200 Subject: [PATCH 27/31] Restore vergen --- Cargo.toml | 3 +++ build.rs | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 57f13df4..c8769ab7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,9 @@ uint = "0.9.5" [dev-dependencies] tempfile = "3.8.1" +[build-dependencies] +vergen = { version = "7", default-features = false, features = ["git"] } + [patch.crates-io] casper-hashing = { git = "https://github.com/casper-network/casper-node", branch = "dev" } casper-types = { git = "https://github.com/casper-network/casper-node", branch = "dev" } diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..c8dd712c --- /dev/null +++ b/build.rs @@ -0,0 +1,7 @@ +use vergen::{Config, ShaKind}; + +fn main() { + let mut config = Config::default(); + *config.git_mut().sha_kind_mut() = ShaKind::Short; + let _ = vergen::vergen(config); +} From 44fef2de9f05ae08ca0c7562410fd68ce2a6859c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 24 Jun 2024 10:17:35 +0200 Subject: [PATCH 28/31] Use correct build.rs --- Cargo.toml | 3 --- build.rs | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c8769ab7..57f13df4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,6 @@ uint = "0.9.5" [dev-dependencies] tempfile = "3.8.1" -[build-dependencies] -vergen = { version = "7", default-features = false, features = ["git"] } - [patch.crates-io] casper-hashing = { git = "https://github.com/casper-network/casper-node", branch = "dev" } casper-types = { git = "https://github.com/casper-network/casper-node", branch = "dev" } diff --git a/build.rs b/build.rs index c8dd712c..039bc4a0 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,21 @@ -use vergen::{Config, ShaKind}; +use std::process::Command; + +const GIT_HASH_ENV_VAR: &str = "GIT_SHA_SHORT"; fn main() { - let mut config = Config::default(); - *config.git_mut().sha_kind_mut() = ShaKind::Short; - let _ = vergen::vergen(config); + //Build command to retrieve the short git commit hash + let git_process_output = Command::new("git") + .arg("rev-parse") + .arg("--short") + .arg("HEAD") + .output() + .expect("Failed to retrieve short git commit hash"); + + //Parse the raw output into a string, we still need to remove the newline character + let git_hash_raw = + String::from_utf8(git_process_output.stdout).expect("Failed to convert git hash to string"); + //Remove the newline character from the short git commit hash + let git_hash = git_hash_raw.trim_end_matches('\n'); + + println!("cargo:rustc-env={}={}", GIT_HASH_ENV_VAR, git_hash); } From 258db324d3a43dce268dae0b64ff44827951b8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Wed, 26 Jun 2024 21:11:43 +0200 Subject: [PATCH 29/31] Fix build without default features. Now tokio is required with "time" feature. Note that reqwest is not optional and depends on tokio with "net" and "time" features enabled, so there is no additional burden. --- Cargo.toml | 3 ++- lib/lib.rs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57f13df4..a2c09470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ required-features = ["async-trait", "clap", "clap_complete", "tokio", "std-fs-io [features] default = ["async-trait", "clap", "clap_complete", "tokio", "std-fs-io"] +tokio = ["tokio/macros", "tokio/rt", "tokio/sync"] std-fs-io = ["casper-types/std-fs-io"] [dependencies] @@ -48,7 +49,7 @@ serde = { version = "1.0.193", default-features = false, features = ["derive"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } tar = { version = "0.4.41", default-features = false } thiserror = "1.0.50" -tokio = { version = "1.34.0", optional = true, features = ["macros", "rt", "sync", "time"] } +tokio = { version = "1.38.0", features = ["time"] } uint = "0.9.5" [dev-dependencies] diff --git a/lib/lib.rs b/lib/lib.rs index 8829c2cf..d6b33549 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -51,12 +51,11 @@ mod verbosity; mod verification; mod verification_types; -use std::env::current_dir; +use std::{env::current_dir, path::Path}; #[cfg(feature = "std-fs-io")] use std::{ fs, io::{Cursor, Read, Write}, - path::Path, }; #[cfg(feature = "std-fs-io")] From c047567daa42eb54413e10a6098bb6957cc6bf1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 27 Jun 2024 15:24:12 +0200 Subject: [PATCH 30/31] Fix wasm build --- lib/cli/json_args.rs | 44 ++++++++++++++++++++++---------------------- lib/verification.rs | 22 ++++++++++++++-------- src/main.rs | 4 ++-- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/lib/cli/json_args.rs b/lib/cli/json_args.rs index 00c88282..2ba4e1aa 100644 --- a/lib/cli/json_args.rs +++ b/lib/cli/json_args.rs @@ -59,59 +59,59 @@ fn write_json_to_bytesrepr( .as_i64() .and_then(|value| i32::try_from(value).ok()) .ok_or(ErrorDetails::CannotParseToI32)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::I64, Value::Number(number)) => { let value = number.as_i64().ok_or(ErrorDetails::CannotParseToI64)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U8, Value::Number(number)) => { let value = number .as_u64() .and_then(|value| u8::try_from(value).ok()) .ok_or(ErrorDetails::CannotParseToU8)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U32, Value::Number(number)) => { let value = number .as_u64() .and_then(|value| u32::try_from(value).ok()) .ok_or(ErrorDetails::CannotParseToU32)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U64, Value::Number(number)) => { let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U128, Value::String(string)) => { let value = U128::from_dec_str(string)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U128, Value::Number(number)) => { let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?; - U128::from(value).write_bytes(output)? + U128::from(value).write_bytes(output)?; } (&CLType::U256, Value::String(string)) => { let value = U256::from_dec_str(string)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U256, Value::Number(number)) => { let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?; - U256::from(value).write_bytes(output)? + U256::from(value).write_bytes(output)?; } (&CLType::U512, Value::String(string)) => { let value = U512::from_dec_str(string)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::U512, Value::Number(number)) => { let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?; - U512::from(value).write_bytes(output)? + U512::from(value).write_bytes(output)?; } (&CLType::Unit, Value::Null) => (), (&CLType::String, Value::String(string)) => string.write_bytes(output)?, (&CLType::Key, Value::String(string)) => { let value = Key::from_formatted_str(string)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::Key, Value::Object(map)) => { // This is an alternative JSON representation of a `Key`, e.g. if calling @@ -141,22 +141,22 @@ fn write_json_to_bytesrepr( Key::ChainspecRegistry if mapped_variant == "ChainspecRegistry" => {} _ => return Err(ErrorDetails::KeyObjectHasInvalidVariant), } - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::URef, Value::String(string)) => { let value = URef::from_formatted_str(string)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (&CLType::PublicKey, Value::String(string)) => { let value = PublicKey::from_hex(string)?; - value.write_bytes(output)? + value.write_bytes(output)?; } (CLType::Option(ref _inner_cl_type), Value::Null) => { output.push(OPTION_NONE_TAG); } (CLType::Option(ref inner_cl_type), _) => { output.push(OPTION_SOME_TAG); - write_json_to_bytesrepr(inner_cl_type, json_value, output)? + write_json_to_bytesrepr(inner_cl_type, json_value, output)?; } (CLType::List(ref inner_cl_type), Value::Array(vec)) => { (vec.len() as u32).write_bytes(output)?; @@ -209,11 +209,11 @@ fn write_json_to_bytesrepr( match map.iter().next() { Some((key, value)) if key.to_ascii_lowercase() == "ok" => { output.push(RESULT_OK_TAG); - write_json_to_bytesrepr(ok, value, output)? + write_json_to_bytesrepr(ok, value, output)?; } Some((key, value)) if key.to_ascii_lowercase() == "err" => { output.push(RESULT_ERR_TAG); - write_json_to_bytesrepr(err, value, output)? + write_json_to_bytesrepr(err, value, output)?; } _ => return Err(ErrorDetails::ResultObjectHasInvalidVariant), } @@ -243,7 +243,7 @@ fn write_json_to_bytesrepr( _ => return Err(ErrorDetails::MapTypeNotValidAsObject(*key_type.clone())), }; (map.len() as u32).write_bytes(output)?; - for (key_as_str, value) in map.iter() { + for (key_as_str, value) in map { let key = match **key_type { CLType::I32 => json!(i32::from_str(key_as_str)?), CLType::I64 => json!(i64::from_str(key_as_str)?), @@ -294,7 +294,7 @@ fn write_json_to_bytesrepr( actual: vec.len(), }); } - write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)? + write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?; } (CLType::Tuple2(ref inner_cl_types), Value::Array(vec)) => { if vec.len() != inner_cl_types.len() { @@ -304,7 +304,7 @@ fn write_json_to_bytesrepr( }); } write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?; - write_json_to_bytesrepr(&inner_cl_types[1], &vec[1], output)? + write_json_to_bytesrepr(&inner_cl_types[1], &vec[1], output)?; } (CLType::Tuple3(ref inner_cl_types), Value::Array(vec)) => { if vec.len() != inner_cl_types.len() { @@ -315,7 +315,7 @@ fn write_json_to_bytesrepr( } write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?; write_json_to_bytesrepr(&inner_cl_types[1], &vec[1], output)?; - write_json_to_bytesrepr(&inner_cl_types[2], &vec[2], output)? + write_json_to_bytesrepr(&inner_cl_types[2], &vec[2], output)?; } _ => return Err(ErrorDetails::IncompatibleType), }; diff --git a/lib/verification.rs b/lib/verification.rs index 8f8a8617..b6d25808 100644 --- a/lib/verification.rs +++ b/lib/verification.rs @@ -87,16 +87,22 @@ pub async fn send_verification_request( code_archive, }; - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + fn make_client() -> reqwest::Result { + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let builder = ClientBuilder::new() + .default_headers(headers) + .user_agent("casper-client-rs"); - let Ok(http_client) = ClientBuilder::new() - .default_headers(headers) // https://github.com/hyperium/hyper/issues/2136 - .pool_max_idle_per_host(0) - .user_agent("casper-client-rs") - .build() - else { + #[cfg(not(target_arch = "wasm32"))] + let builder = builder.pool_max_idle_per_host(0); + + builder.build() + } + + let Ok(http_client) = make_client() else { eprintln!("Failed to build HTTP client"); return Err(Error::FailedToConstructHttpClient); }; diff --git a/src/main.rs b/src/main.rs index e92ca768..32793126 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,7 @@ const APP_NAME: &str = "Casper client"; static VERSION: Lazy = Lazy::new( - || match option_env!("GIT_SHA_SHORT").map(|sha| sha.to_lowercase()) { + || match option_env!("GIT_SHA_SHORT").map(str::to_lowercase) { None => crate_version!().to_string(), Some(git_sha_short) => { if git_sha_short.to_lowercase() == "unknown" { @@ -193,7 +193,7 @@ async fn main() { let mut verbosity_level = common::verbose::get(matches); if verbosity_level == 0 { - verbosity_level += 1 + verbosity_level += 1; } match result { From deaa5290b3b380f7b3e477720f732475df1260fd Mon Sep 17 00:00:00 2001 From: gRoussac Date: Thu, 27 Jun 2024 20:41:50 +0200 Subject: [PATCH 31/31] Fix CI-CD tokio not optional --- Cargo.toml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2c09470..a8ff1901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "casper-client" version = "2.0.0" # when updating, also update 'html_root_url' in lib.rs -authors = ["Marc Brinkmann ", "Fraser Hutchison ", "Zachary Showalter "] +authors = [ + "Marc Brinkmann ", + "Fraser Hutchison ", + "Zachary Showalter ", +] edition = "2021" description = "A client library and binary for interacting with the Casper network" documentation = "https://docs.rs/casper-client" @@ -19,11 +23,10 @@ path = "lib/lib.rs" name = "casper-client" path = "src/main.rs" doc = false -required-features = ["async-trait", "clap", "clap_complete", "tokio", "std-fs-io"] +required-features = ["async-trait", "clap", "clap_complete", "std-fs-io"] [features] -default = ["async-trait", "clap", "clap_complete", "tokio", "std-fs-io"] -tokio = ["tokio/macros", "tokio/rt", "tokio/sync"] +default = ["async-trait", "clap", "clap_complete", "std-fs-io"] std-fs-io = ["casper-types/std-fs-io"] [dependencies] @@ -33,9 +36,9 @@ base64 = { version = "0.22.1", default-features = false } bytes = { version = "1.6.0", default-features = false } casper-hashing = "3.0.0" casper-types = { version = "4.0.1", features = ["std"] } -clap = { version = "~4.4", optional = true, features = ["cargo", "deprecated", "wrap_help"] } +clap = { version = "~4.4", optional = true, features = ["cargo", "deprecated"] } clap_complete = { version = "<4.5.0", optional = true } -flate2 = "1.0.28" +flate2 = "1.0.30" hex-buffer-serde = "0.4.0" humantime = "2.1.0" itertools = "0.12.0" @@ -43,13 +46,13 @@ jsonrpc-lite = "0.6.0" num-traits = "0.2.15" once_cell = "1.18.0" rand = "0.8.5" -reqwest = { version = "0.12.3", features = ["json"] } +reqwest = { version = "0.12.4", features = ["json"] } schemars = "=0.8.5" serde = { version = "1.0.193", default-features = false, features = ["derive"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } tar = { version = "0.4.41", default-features = false } -thiserror = "1.0.50" -tokio = { version = "1.38.0", features = ["time"] } +thiserror = "1" +tokio = { version = "1.38.0", features = ["macros", "rt", "sync", "time"] } uint = "0.9.5" [dev-dependencies]