diff --git a/ow_data_provider_cli/.gitignore b/ow_data_provider_cli/.gitignore index 3b507eb..f48c1c9 100644 --- a/ow_data_provider_cli/.gitignore +++ b/ow_data_provider_cli/.gitignore @@ -1,2 +1,3 @@ .env -/target/ \ No newline at end of file +/target/ +/output_files/ \ No newline at end of file diff --git a/ow_data_provider_cli/Cargo.lock b/ow_data_provider_cli/Cargo.lock index 300ae01..2be6bb1 100644 --- a/ow_data_provider_cli/Cargo.lock +++ b/ow_data_provider_cli/Cargo.lock @@ -781,6 +781,12 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "auto_impl" version = "1.2.0" @@ -942,6 +948,17 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1166,6 +1183,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1397,6 +1423,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1493,6 +1538,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1503,6 +1549,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1579,6 +1642,15 @@ dependencies = [ "serde", ] +[[package]] +name = "infer" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" +dependencies = [ + "cfb", +] + [[package]] name = "interprocess" version = "2.2.1" @@ -1726,6 +1798,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1900,8 +1982,13 @@ dependencies = [ "alloy", "c-kzg", "dotenvy", + "infer", "ow_blob_codec", + "quick-xml", + "reqwest", + "serde", "tokio", + "tokio-util", ] [[package]] @@ -2201,30 +2288,34 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", @@ -2234,12 +2325,15 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", + "system-configuration", "tokio", "tokio-native-tls", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "windows-registry", ] @@ -2740,6 +2834,27 @@ dependencies = [ "futures-core", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -3033,6 +3148,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -3083,6 +3207,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + [[package]] name = "valuable" version = "0.1.0" @@ -3192,6 +3322,19 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.70" diff --git a/ow_data_provider_cli/Cargo.toml b/ow_data_provider_cli/Cargo.toml index d9d423e..ad3957e 100644 --- a/ow_data_provider_cli/Cargo.toml +++ b/ow_data_provider_cli/Cargo.toml @@ -4,8 +4,13 @@ version = "0.1.0" edition = "2021" [dependencies] +reqwest = { version = "0.12.8", features = ["stream", "multipart", "json"] } alloy = { version = "0.3.6", features = ["full"] } c-kzg = "1.0.3" dotenvy = "0.15.7" ow_blob_codec = "0.1.1" tokio = { version = "1.40.0", features = ["full"] } +tokio-util = "0.7.12" +serde = "1.0.210" +infer = "0.16.0" +quick-xml = "0.36.2" diff --git a/ow_data_provider_cli/src/blob.rs b/ow_data_provider_cli/src/blob.rs index 0fabb96..f686b0f 100644 --- a/ow_data_provider_cli/src/blob.rs +++ b/ow_data_provider_cli/src/blob.rs @@ -1,4 +1,4 @@ -use crate::{errors::OwDataProviderCliError, Config}; +use crate::{constants::OUTPUT_FILES_DIR, errors::OwDataProviderCliError}; use alloy::consensus::BlobTransactionSidecar; use c_kzg::{ethereum_kzg_settings, Blob, KzgCommitment, KzgProof}; use std::error::Error; @@ -9,8 +9,8 @@ pub struct BlobTransactionData { } impl BlobTransactionData { - pub fn build(config: &Config) -> Result> { - let blob: [u8; 131072] = ow_blob_codec::blob_from_dir(&config.folder_path)?; + pub fn build() -> Result> { + let blob: [u8; 131072] = ow_blob_codec::blob_from_dir(OUTPUT_FILES_DIR)?; let kzg_blob = Blob::new(blob); diff --git a/ow_data_provider_cli/src/constants.rs b/ow_data_provider_cli/src/constants.rs index 47d5735..91a6bd1 100644 --- a/ow_data_provider_cli/src/constants.rs +++ b/ow_data_provider_cli/src/constants.rs @@ -1,3 +1,8 @@ use alloy::primitives::{address, Address}; pub const DDEX_SEQUENCER_ADDRESS: Address = address!("B965D10739e19a9158e7f713720B0145D996E370"); +pub const IPFS_API_BASE_URL: &str = "http://localhost:5001"; +pub const IPFS_API_ADD_FILE: &str = "/api/v0/add"; +pub const OUTPUT_FILES_DIR: &str = "./output_files"; +pub const IPFS_CIDS_ROOT_TAG: &str = "MessageHeader"; +pub const IMAGE_FILE_CID_TAG: &str = "ImageIpfsCid"; diff --git a/ow_data_provider_cli/src/errors.rs b/ow_data_provider_cli/src/errors.rs index 68188c5..09af7a9 100644 --- a/ow_data_provider_cli/src/errors.rs +++ b/ow_data_provider_cli/src/errors.rs @@ -6,6 +6,9 @@ pub enum OwDataProviderCliError { MissingEnvVar(String), MissingCliArg(String), InvalidBlobProof(), + SourcePathIsNotDir(String), + EmptySourcePathFolder(String), + ErrorReadingFile(String), } impl fmt::Display for OwDataProviderCliError { @@ -20,6 +23,15 @@ impl fmt::Display for OwDataProviderCliError { Self::InvalidBlobProof() => { write!(f, "c_kzg error during proof validation") } + Self::SourcePathIsNotDir(path) => { + write!(f, "Provided folder_path is not a directory: {}", path) + } + Self::EmptySourcePathFolder(path) => { + write!(f, "Folder under provided folder_path is empty: {}", path) + } + Self::ErrorReadingFile(file_path) => { + write!(f, "Error while reading file from: {}", file_path) + } } } } diff --git a/ow_data_provider_cli/src/ipfs.rs b/ow_data_provider_cli/src/ipfs.rs new file mode 100644 index 0000000..32427d9 --- /dev/null +++ b/ow_data_provider_cli/src/ipfs.rs @@ -0,0 +1,147 @@ +use reqwest::{multipart, Body}; +use serde::Deserialize; +use std::fs; +use std::path::PathBuf; +use std::{ + error::Error, + io::{BufReader, BufWriter}, + path::Path, +}; +use tokio_util::codec::{BytesCodec, FramedRead}; + +use crate::constants::{IMAGE_FILE_CID_TAG, IPFS_CIDS_ROOT_TAG, OUTPUT_FILES_DIR}; +use crate::{ + constants::{IPFS_API_ADD_FILE, IPFS_API_BASE_URL}, + errors::OwDataProviderCliError, +}; + +use quick_xml::events::{BytesStart, BytesText, Event}; +use quick_xml::Reader; +use quick_xml::Writer; + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +struct IpfsResponse { + Hash: String, +} + +struct AttachCidAndSaveInput { + image_file_cid: String, + xml_file_path: String, + output_file_name: String, +} + +async fn file_to_multipart_form(file_path: String) -> Result> { + let file = tokio::fs::File::open(file_path).await?; + let file_stream = FramedRead::new(file, BytesCodec::new()); + let multipart_stream = multipart::Part::stream(Body::wrap_stream(file_stream)); + let multipart_form = multipart::Form::new().part("file", multipart_stream); + Ok(multipart_form) +} + +pub async fn pin_file(file_path: String) -> Result> { + let multipart_form = file_to_multipart_form(file_path).await?; + let client = reqwest::Client::new(); + + let response = client + .post(format!("{}{}", IPFS_API_BASE_URL, IPFS_API_ADD_FILE)) + .multipart(multipart_form) + .send() + .await?; + + let result = response.json::().await?; + + Ok(result.Hash) +} + +async fn attach_cid_and_save(input: AttachCidAndSaveInput) -> Result<(), Box> { + let mut buf = Vec::new(); + + let file = fs::File::open(&input.xml_file_path)?; + let reader = BufReader::new(file); + let mut reader = Reader::from_reader(reader); + + fs::create_dir_all(OUTPUT_FILES_DIR)?; + let output_file = fs::File::create(format!("{}/{}", OUTPUT_FILES_DIR, input.output_file_name))?; + let writer = BufWriter::new(output_file); + let mut writer = Writer::new(writer); + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Eof) => break, + Ok(Event::Start(ref e)) => { + writer.write_event(Event::Start(e.to_owned()))?; + if e == &BytesStart::new(IPFS_CIDS_ROOT_TAG) { + let tag = BytesStart::new(IMAGE_FILE_CID_TAG); + writer.write_event(Event::Start(tag.to_owned()))?; + writer.write_event(Event::Text(BytesText::new(&input.image_file_cid)))?; + writer.write_event(Event::End(tag.to_end()))?; + } + } + Ok(Event::Text(ref e)) => { + writer.write_event(Event::Text(e.to_owned()))?; + } + Ok(Event::End(ref e)) => { + writer.write_event(Event::End(e.to_owned()))?; + } + + Err(e) => return Err(Box::new(e)), + _ => {} + } + buf.clear(); + } + + Ok(()) +} + +async fn process_asset_folder( + asset_folder_path: PathBuf, + folder_index: usize, +) -> Result<(), Box> { + if asset_folder_path.is_dir() { + let asset_files = fs::read_dir(asset_folder_path)?; + let mut xml_file_path = String::new(); + let mut image_file_cid = String::new(); + for asset_file in asset_files { + let asset_path = asset_file?.path(); + if asset_path.is_dir() == false { + let kind = infer::get_from_path(&asset_path)?.ok_or_else(|| { + Box::new(OwDataProviderCliError::ErrorReadingFile( + asset_path.to_string_lossy().to_string(), + )) + })?; + if kind.mime_type().starts_with("image/") { + image_file_cid = pin_file(asset_path.to_string_lossy().to_string()).await?; + } + if kind.extension() == "xml" { + xml_file_path = asset_path.to_string_lossy().to_string(); + } + } + } + attach_cid_and_save(AttachCidAndSaveInput { + image_file_cid, + xml_file_path, + output_file_name: format!("{}.xml", folder_index), + }) + .await?; + } + Ok(()) +} + +pub async fn create_output_files(folder_path: &String) -> Result<(), Box> { + let root_folder_dir = Path::new(&folder_path); + + if root_folder_dir.is_dir() { + let asset_folders = fs::read_dir(root_folder_dir)?; + + for (index, asset_folder) in asset_folders.into_iter().enumerate() { + let asset_folder_path = asset_folder?.path(); + process_asset_folder(asset_folder_path, index).await?; + } + } else { + return Err(Box::new(OwDataProviderCliError::SourcePathIsNotDir( + root_folder_dir.to_string_lossy().to_string(), + ))); + } + Ok(()) +} diff --git a/ow_data_provider_cli/src/lib.rs b/ow_data_provider_cli/src/lib.rs index ad29f7a..4a24fc9 100644 --- a/ow_data_provider_cli/src/lib.rs +++ b/ow_data_provider_cli/src/lib.rs @@ -2,6 +2,7 @@ mod blob; mod constants; mod ddex_sequencer; mod errors; +mod ipfs; use alloy::network::EthereumWallet; use alloy::providers::ProviderBuilder; @@ -54,6 +55,8 @@ impl Config { } pub async fn run(config: Config) -> Result<(), Box> { + // let ipfs_cid = ipfs::pin_file("./tests/test.xml".to_string()).await?; + ipfs::create_output_files(&config.folder_path).await?; let private_key_signer: PrivateKeySigner = config .private_key .parse() @@ -66,7 +69,7 @@ pub async fn run(config: Config) -> Result<(), Box> { .on_http(config.rpc_url.parse().unwrap()); let ddex_sequencer_context = DdexSequencerContext::build(&provider).await?; - let blob_transaction_data = BlobTransactionData::build(&config).unwrap(); + let blob_transaction_data = BlobTransactionData::build().unwrap(); println!("sending tx..."); ddex_sequencer_context .send_blob(blob_transaction_data) diff --git a/ow_data_provider_cli/tests/asset_one/image-asset.png b/ow_data_provider_cli/tests/asset_one/image-asset.png new file mode 100644 index 0000000..f885f5c Binary files /dev/null and b/ow_data_provider_cli/tests/asset_one/image-asset.png differ diff --git a/ow_data_provider_cli/tests/test.xml b/ow_data_provider_cli/tests/asset_one/test.xml similarity index 100% rename from ow_data_provider_cli/tests/test.xml rename to ow_data_provider_cli/tests/asset_one/test.xml diff --git a/ow_data_provider_cli/tests/asset_two/name.xml b/ow_data_provider_cli/tests/asset_two/name.xml new file mode 100644 index 0000000..6862a25 --- /dev/null +++ b/ow_data_provider_cli/tests/asset_two/name.xml @@ -0,0 +1,343 @@ + + + + + + + + + MSG12345-01 + + PADPIDA2014120301 + + Example Sender + + + + PADPIDA2024072201A + + Original Works + + + 2024-09-24T14:57:25+01:00 + + + + + + + PADPIDA2014120301 + Example Sender + + + + + + + PADPIDA2014120301 + + 0000000080549162 + PADPIDA2014120301 + + + + PADPIDA2014120301 + MusicLicensingCompany + Worldwide + + 2023 + 2025 + + ReproductionRight + MakeAvailableRight + 100 + + + + + PExampleReference1 + + Example Rights Holder + Example Rights Holder + + + + PExampleReference2 + + Artist Namer + Example Artist Namer + + + + + + + + + + + A1 + MusicalWorkSoundRecording + + ImmersiveEdition + + PL1234567890 + + + + + 2024 + 2024 My company, LLC + + Stereo + + T1 + + AudioFile + + bafybeigdyrztfdtymg4vwyoklvgv27bmwypc33m2gw4npyulohj2bkq6za + + + UserDefined + + + + + + UserDefined + + + + + + + Acoustic + + + T-123.456.789.Z + + + + Example Sound Recording + Example Subtitle + + + + + AlbumVersion + + + PExampleReference2 + Artist + + + true + + + + PExampleReference2 + + Author + + + + + PExampleReference2 + + + + PExampleReference1 + RoyaltyAdministrator + + PT2M28S + 1993-12-01 + NoAdviceAvailable + false + ja + + + + + A2 + FrontCoverImage + + REVELATOR_FILE23323 + + NotExplicit + + T22 + + bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku + + + UserDefined + + + + + + + + + + R0 + Album + + + G123456789012345678 + 1234567890123 + + Title + + Great Title + + Saeko Shu + しゅうさえこ + + PExampleReference2 + MainArtist + + PExampleReference1 + PT36M30S + + + + + NoAdviceAvailable + + + + + + + + 1 + true + + PExampleReference2 + Artist + + + true + + + + 1 + A1 + + + 2 + A2 + + + + + R1 + + 00094631432057_JPTO09404900_R1 + + A1 + PExampleReference2 + + + + + + + R2 + + 00094631432057_JPTO09404910_R2 + + A2 + PExampleReference2 + + + + + + + diff --git a/ow_data_provider_cli/tests/asset_two/random-image.avif b/ow_data_provider_cli/tests/asset_two/random-image.avif new file mode 100644 index 0000000..2d95c16 Binary files /dev/null and b/ow_data_provider_cli/tests/asset_two/random-image.avif differ diff --git a/ow_validator_node/src/circuit_mock.rs b/ow_validator_node/src/circuit_mock.rs index 1c2cb88..2dbb40f 100644 --- a/ow_validator_node/src/circuit_mock.rs +++ b/ow_validator_node/src/circuit_mock.rs @@ -3,22 +3,32 @@ use quick_xml::{ Reader, }; -use crate::{ddex_sequencer::DdexSequencer::DdexMessageData, errors::OwValidatorNodeError}; +use crate::{ + constants::IMAGE_FILE_CID_TAG, ddex_sequencer::DdexSequencer::DdexMessageData, + errors::OwValidatorNodeError, +}; use std::{error::Error, io::Cursor}; +pub struct ExtractedMessageData { + pub emittable_data: DdexMessageData, + pub image_ipfs_cid: String, +} + pub fn extract_message_data( decoded_blob: &Vec>, -) -> Result, Box> { - let mut result = Vec::::new(); +) -> Result, Box> { + let mut result = Vec::::new(); let mut reader: Reader>>; let mut buffer = Vec::new(); let mut inside_isrc_tag = false; let mut inside_release_id_tag = false; + let mut inside_image_file_cid_tag = false; - let mut isrc_tag_value: String = String::new(); - let mut release_id_tag_value: String = String::new(); + let mut isrc_tag_value = String::new(); + let mut release_id_tag_value = String::new(); + let mut image_file_cid_value = String::new(); for ddex_message in decoded_blob { reader = Reader::from_reader(Cursor::new(&ddex_message)); @@ -32,18 +42,27 @@ pub fn extract_message_data( Ok(Event::Start(e)) if e == BytesStart::new("GRid") => { inside_release_id_tag = true; } + Ok(Event::Start(e)) if e == BytesStart::new(IMAGE_FILE_CID_TAG) => { + inside_image_file_cid_tag = true; + } Ok(Event::Text(e)) if inside_isrc_tag => { isrc_tag_value = String::from_utf8(e.to_vec())?; } Ok(Event::Text(e)) if inside_release_id_tag => { release_id_tag_value = String::from_utf8(e.to_vec())?; } + Ok(Event::Text(e)) if inside_image_file_cid_tag => { + image_file_cid_value = String::from_utf8(e.to_vec())?; + } Ok(Event::End(e)) if e == BytesEnd::new("ISRC") => { inside_isrc_tag = false; } Ok(Event::End(e)) if e == BytesEnd::new("GRid") => { inside_release_id_tag = false; } + Ok(Event::End(e)) if e == BytesEnd::new(IMAGE_FILE_CID_TAG) => { + inside_image_file_cid_tag = false; + } Ok(_) => (), Err(_) => { return Err(Box::new(OwValidatorNodeError::InvalidHexStringValue( @@ -54,9 +73,12 @@ pub fn extract_message_data( buffer.clear(); } - result.push(DdexMessageData { - isrc: isrc_tag_value.clone(), - releaseId: release_id_tag_value.clone(), + result.push(ExtractedMessageData { + emittable_data: DdexMessageData { + isrc: isrc_tag_value.clone(), + releaseId: release_id_tag_value.clone(), + }, + image_ipfs_cid: image_file_cid_value.clone(), }); isrc_tag_value = String::new(); diff --git a/ow_validator_node/src/constants.rs b/ow_validator_node/src/constants.rs index ab8a0e5..ccaa34d 100644 --- a/ow_validator_node/src/constants.rs +++ b/ow_validator_node/src/constants.rs @@ -4,3 +4,6 @@ pub const DDEX_SEQUENCER_ADDRESS: Address = address!("B965D10739e19a9158e7f71372 pub const GET_BEACON_BLOCK_API_PATH: &str = "/eth/v2/beacon/blocks/"; pub const GET_SIDECARS_API_PATH: &str = "/eth/v1/beacon/blob_sidecars/"; pub const EMPTY_QUEUE_HEAD: FixedBytes<32> = FixedBytes::repeat_byte(0); +pub const IMAGE_FILE_CID_TAG: &str = "ImageIpfsCid"; +pub const IPFS_API_BASE_URL: &str = "http://localhost:5001"; +pub const IPFS_API_CAT_FILE: &str = "/api/v0/cat"; diff --git a/ow_validator_node/src/errors.rs b/ow_validator_node/src/errors.rs index 0da37eb..e79a421 100644 --- a/ow_validator_node/src/errors.rs +++ b/ow_validator_node/src/errors.rs @@ -9,6 +9,7 @@ pub enum OwValidatorNodeError { FailedToFindBlobSidecar(String), InvalidBlobLength(usize), InvalidHexStringValue(String), + ImageFileNotFoundInIpfs(String), } impl fmt::Display for OwValidatorNodeError { @@ -44,6 +45,9 @@ impl fmt::Display for OwValidatorNodeError { Self::InvalidHexStringValue(hex_byte) => { write!(f, "Invalid hex value in blob String: {}", hex_byte) } + Self::ImageFileNotFoundInIpfs(cid) => { + write!(f, "Image file not found in IPFS node: {}", cid) + } } } } diff --git a/ow_validator_node/src/ipfs.rs b/ow_validator_node/src/ipfs.rs new file mode 100644 index 0000000..520beef --- /dev/null +++ b/ow_validator_node/src/ipfs.rs @@ -0,0 +1,27 @@ +use std::error::Error; + +use crate::{ + constants::{IPFS_API_BASE_URL, IPFS_API_CAT_FILE}, + errors::OwValidatorNodeError, +}; + +pub async fn check_file_accessibility(cids: Vec) -> Result<(), Box> { + println!("{cids:?}"); + let client = reqwest::Client::new(); + + for cid in cids { + let response = client + .post(format!( + "{}{}?arg={}", + IPFS_API_BASE_URL, IPFS_API_CAT_FILE, cid + )) + .send() + .await?; + + if response.status() != 200 { + return Err(Box::new(OwValidatorNodeError::ImageFileNotFoundInIpfs(cid))); + } + } + + Ok(()) +} diff --git a/ow_validator_node/src/lib.rs b/ow_validator_node/src/lib.rs index a9fef9d..03352ff 100644 --- a/ow_validator_node/src/lib.rs +++ b/ow_validator_node/src/lib.rs @@ -1,9 +1,9 @@ mod beacon_chain; mod circuit_mock; mod constants; - mod ddex_sequencer; mod errors; +mod ipfs; use alloy::network::{Ethereum, EthereumWallet}; use alloy::primitives::{Bytes, FixedBytes}; @@ -105,10 +105,23 @@ async fn validate_blobs( let decoded = ow_blob_codec::decoder::blob_to_vecs(blob).unwrap(); let ddex_messages_data = circuit_mock::extract_message_data(&decoded)?; + + let tx_input = ddex_messages_data + .iter() + .map(|emittable_values| emittable_values.emittable_data.clone()) + .collect(); + + let ipfs_cids = ddex_messages_data + .iter() + .map(|emittable_values| emittable_values.image_ipfs_cid.clone()) + .collect(); + + ipfs::check_file_accessibility(ipfs_cids).await?; + println!("sending tx..."); let receipt = ddex_sequencer_context .contract - .submitProofOfProcessing(true, ddex_messages_data) + .submitProofOfProcessing(true, tx_input) .send() .await? .get_receipt()