From 3bb43399c4c348d6c15b5b9b2ae21176351056c7 Mon Sep 17 00:00:00 2001 From: Rim Rakhimov Date: Tue, 28 Jan 2025 16:41:39 +0400 Subject: [PATCH] feat(verifier-alliance-database): check that inseted verified contract is better than existing ones (#1214) --- .../verifier-alliance-database/Cargo.toml | 2 +- .../src/internal.rs | 78 ++++++++ .../verifier-alliance-database/src/lib.rs | 13 ++ .../tests/integration/verified_contracts.rs | 179 +++++++++++++++++- 4 files changed, 262 insertions(+), 10 deletions(-) diff --git a/eth-bytecode-db/verifier-alliance-database/Cargo.toml b/eth-bytecode-db/verifier-alliance-database/Cargo.toml index ba72aa37a..a3653538e 100644 --- a/eth-bytecode-db/verifier-alliance-database/Cargo.toml +++ b/eth-bytecode-db/verifier-alliance-database/Cargo.toml @@ -12,7 +12,7 @@ serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } sha3 = { workspace = true } -strum = { workspace = true } +strum = { workspace = true, features = ["std"] } verification-common = { workspace = true } verifier-alliance-entity = { workspace = true } diff --git a/eth-bytecode-db/verifier-alliance-database/src/internal.rs b/eth-bytecode-db/verifier-alliance-database/src/internal.rs index bbd95f447..b1af1be0a 100644 --- a/eth-bytecode-db/verifier-alliance-database/src/internal.rs +++ b/eth-bytecode-db/verifier-alliance-database/src/internal.rs @@ -550,6 +550,84 @@ pub fn try_models_into_verified_contract( }) } +pub use compare_matches::should_store_the_match; +mod compare_matches { + use super::*; + + #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord)] + enum MatchStatus { + NoMatch, + WithoutMetadata, + WithMetadata, + } + + impl From<&Match> for MatchStatus { + fn from(value: &Match) -> Self { + if value.metadata_match { + MatchStatus::WithMetadata + } else { + MatchStatus::WithoutMetadata + } + } + } + + fn status_from_model_match(does_match: bool, does_metadata_match: Option) -> MatchStatus { + if !does_match { + return MatchStatus::NoMatch; + } + if let Some(true) = does_metadata_match { + return MatchStatus::WithMetadata; + } + MatchStatus::WithoutMetadata + } + + pub async fn should_store_the_match( + database_connection: &C, + contract_deployment_id: Uuid, + potential_matches: &VerifiedContractMatches, + ) -> Result { + let (potential_creation_match, potential_runtime_match) = match potential_matches { + VerifiedContractMatches::OnlyCreation { creation_match } => { + (creation_match.into(), MatchStatus::NoMatch) + } + VerifiedContractMatches::OnlyRuntime { runtime_match } => { + (MatchStatus::NoMatch, runtime_match.into()) + } + VerifiedContractMatches::Complete { + creation_match, + runtime_match, + } => (creation_match.into(), runtime_match.into()), + }; + + // We want to store all perfect matches even if there are other ones in the database. + // That should be impossible, but in case that happens we are interested in storing them all + // in order to manually review them later. + if potential_creation_match == MatchStatus::WithMetadata + || potential_runtime_match == MatchStatus::WithMetadata + { + return Ok(true); + } + + let is_model_worse = |model: &verified_contracts::Model| { + let model_creation_match = + status_from_model_match(model.creation_match, model.creation_metadata_match); + let model_runtime_match = + status_from_model_match(model.runtime_match, model.runtime_metadata_match); + model_creation_match < potential_creation_match + || model_runtime_match < potential_runtime_match + }; + let existing_verified_contracts = retrieve_verified_contracts_by_deployment_id( + database_connection, + contract_deployment_id, + ) + .await?; + let should_potential_match_be_stored = + existing_verified_contracts.iter().all(is_model_worse); + + Ok(should_potential_match_be_stored) + } +} + fn extract_match_from_model( metadata_match: bool, transformations: Value, diff --git a/eth-bytecode-db/verifier-alliance-database/src/lib.rs b/eth-bytecode-db/verifier-alliance-database/src/lib.rs index 29942d771..4db7b860e 100644 --- a/eth-bytecode-db/verifier-alliance-database/src/lib.rs +++ b/eth-bytecode-db/verifier-alliance-database/src/lib.rs @@ -62,6 +62,19 @@ pub async fn insert_verified_contract( .await .context("begin transaction")?; + // Check that the database does not already have better verification data for the deployment + if !internal::should_store_the_match( + &transaction, + verified_contract.contract_deployment_id, + &verified_contract.matches, + ) + .await? + { + return Err(anyhow!( + "the candidate verified contract is not better than existing" + )); + } + let sources = std::mem::take(&mut verified_contract.compiled_contract.sources); let source_hashes = internal::precalculate_source_hashes(&sources); diff --git a/eth-bytecode-db/verifier-alliance-database/tests/integration/verified_contracts.rs b/eth-bytecode-db/verifier-alliance-database/tests/integration/verified_contracts.rs index efa3e924b..b5cc14856 100644 --- a/eth-bytecode-db/verifier-alliance-database/tests/integration/verified_contracts.rs +++ b/eth-bytecode-db/verifier-alliance-database/tests/integration/verified_contracts.rs @@ -1,15 +1,16 @@ use crate::from_json; use blockscout_display_bytes::decode_hex; use blockscout_service_launcher::test_database::database; -use sea_orm::{prelude::Uuid, DatabaseConnection}; +use pretty_assertions::assert_eq; +use sea_orm::DatabaseConnection; use std::collections::BTreeMap; use verification_common::verifier_alliance::{ CompilationArtifacts, CreationCodeArtifacts, Match, MatchTransformation, MatchValues, RuntimeCodeArtifacts, }; use verifier_alliance_database::{ - CompiledContract, CompiledContractCompiler, CompiledContractLanguage, InsertContractDeployment, - VerifiedContract, VerifiedContractMatches, + CompiledContract, CompiledContractCompiler, CompiledContractLanguage, ContractDeployment, + InsertContractDeployment, VerifiedContract, VerifiedContractMatches, }; use verifier_alliance_migration::Migrator; @@ -17,7 +18,9 @@ use verifier_alliance_migration::Migrator; async fn insert_verified_contract_with_complete_matches_work() { let db_guard = database!(Migrator); - let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await; + let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()) + .await + .id; let compiled_contract = complete_compiled_contract(); let verified_contract = VerifiedContract { contract_deployment_id, @@ -48,7 +51,9 @@ async fn insert_verified_contract_with_complete_matches_work() { async fn insert_verified_contract_with_runtime_only_matches_work() { let db_guard = database!(Migrator); - let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await; + let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()) + .await + .id; let compiled_contract = complete_compiled_contract(); let verified_contract = VerifiedContract { contract_deployment_id, @@ -74,7 +79,9 @@ async fn insert_verified_contract_with_runtime_only_matches_work() { async fn insert_verified_contract_with_creation_only_matches_work() { let db_guard = database!(Migrator); - let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await; + let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()) + .await + .id; let compiled_contract = complete_compiled_contract(); let verified_contract = VerifiedContract { contract_deployment_id, @@ -100,7 +107,9 @@ async fn insert_verified_contract_with_creation_only_matches_work() { async fn insert_verified_contract_with_filled_matches() { let db_guard = database!(Migrator); - let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()).await; + let contract_deployment_id = insert_contract_deployment(db_guard.client().as_ref()) + .await + .id; let compiled_contract = complete_compiled_contract(); let (runtime_match_values, runtime_match_transformations) = { @@ -174,6 +183,157 @@ async fn insert_verified_contract_with_filled_matches() { .expect("error while inserting"); } +#[tokio::test] +async fn inserted_verified_contract_can_be_retrieved() { + let db_guard = database!(Migrator); + let database_connection = db_guard.client(); + let database_connection = database_connection.as_ref(); + + let contract_deployment = insert_contract_deployment(database_connection).await; + let compiled_contract = complete_compiled_contract(); + let verified_contract = VerifiedContract { + contract_deployment_id: contract_deployment.id, + compiled_contract, + matches: VerifiedContractMatches::Complete { + runtime_match: Match { + metadata_match: true, + transformations: vec![], + values: Default::default(), + }, + creation_match: Match { + metadata_match: true, + transformations: vec![], + values: Default::default(), + }, + }, + }; + + verifier_alliance_database::insert_verified_contract( + database_connection, + verified_contract.clone(), + ) + .await + .expect("error while inserting"); + + let retrieved_contracts = verifier_alliance_database::find_verified_contracts( + database_connection, + contract_deployment.chain_id, + contract_deployment.address, + ) + .await + .expect("error while retrieving"); + let retrieved_verified_contracts: Vec<_> = retrieved_contracts + .into_iter() + .map(|value| value.verified_contract) + .collect(); + assert_eq!( + retrieved_verified_contracts, + vec![verified_contract], + "invalid retrieved values" + ); +} + +#[tokio::test] +async fn not_override_partial_matches() { + let db_guard = database!(Migrator); + let database_connection = db_guard.client(); + let database_connection = database_connection.as_ref(); + + let contract_deployment = insert_contract_deployment(database_connection).await; + + let partially_verified_contract = VerifiedContract { + contract_deployment_id: contract_deployment.id, + compiled_contract: complete_compiled_contract(), + matches: VerifiedContractMatches::Complete { + runtime_match: Match { + metadata_match: false, + transformations: vec![], + values: Default::default(), + }, + creation_match: Match { + metadata_match: false, + transformations: vec![], + values: Default::default(), + }, + }, + }; + verifier_alliance_database::insert_verified_contract( + database_connection, + partially_verified_contract.clone(), + ) + .await + .expect("error while inserting partially verified contract"); + + let mut another_partially_verified_contract = partially_verified_contract.clone(); + another_partially_verified_contract + .compiled_contract + .creation_code + .extend_from_slice(&[0x10]); + another_partially_verified_contract + .compiled_contract + .runtime_code + .extend_from_slice(&[0x10]); + verifier_alliance_database::insert_verified_contract( + database_connection, + another_partially_verified_contract.clone(), + ) + .await + .map_err(|err| { + assert!( + err.to_string().contains("is not better than existing"), + "unexpected error: {}", + err + ) + }) + .expect_err("error expected while inserting another partially verified contract"); + + let mut fully_verified_contract = partially_verified_contract.clone(); + fully_verified_contract + .compiled_contract + .creation_code + .extend_from_slice(&[0xff]); + fully_verified_contract + .compiled_contract + .runtime_code + .extend_from_slice(&[0xff]); + fully_verified_contract.matches = VerifiedContractMatches::Complete { + creation_match: Match { + metadata_match: true, + transformations: vec![], + values: Default::default(), + }, + runtime_match: Match { + metadata_match: true, + transformations: vec![], + values: Default::default(), + }, + }; + verifier_alliance_database::insert_verified_contract( + database_connection, + fully_verified_contract.clone(), + ) + .await + .expect("error while inserting fully verified contract"); + + let mut retrieved_contracts = verifier_alliance_database::find_verified_contracts( + database_connection, + contract_deployment.chain_id, + contract_deployment.address, + ) + .await + .expect("error while retrieving"); + retrieved_contracts.sort_by_key(|value| value.created_at); + let retrieved_verified_contracts: Vec<_> = retrieved_contracts + .into_iter() + .map(|value| value.verified_contract) + .collect(); + + assert_eq!( + retrieved_verified_contracts, + vec![partially_verified_contract, fully_verified_contract] + ); +} + fn complete_compiled_contract() -> CompiledContract { CompiledContract { compiler: CompiledContractCompiler::Solc, @@ -209,7 +369,9 @@ fn complete_compiled_contract() -> CompiledContract { } } -async fn insert_contract_deployment(database_connection: &DatabaseConnection) -> Uuid { +async fn insert_contract_deployment( + database_connection: &DatabaseConnection, +) -> ContractDeployment { let contract_deployment = InsertContractDeployment::Regular { chain_id: 10, address: decode_hex("0x8FbB39A5a79aeCE03c8f13ccEE0b96C128ec1a67").unwrap(), @@ -227,5 +389,4 @@ async fn insert_contract_deployment(database_connection: &DatabaseConnection) -> verifier_alliance_database::insert_contract_deployment(database_connection, contract_deployment) .await .expect("error while inserting contract deployment") - .id }