diff --git a/CHANGELOG.md b/CHANGELOG.md index 7168411f0..16c10656f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.5.1] 2024-08-26 ### Added - Added support for the experimental multidapp claimer. - Added the `DAPP_CONTRACT_ADDRESS` environment variable to the `authority-claimer`. If let unset, the service instantiates the MultidappClaimer, that reads dapp addresses from Redis. +- Added `READER_MODE_ENABLED` environment variable to control whether the node generate claims or not. ### Changed - Redacted the contents of `CARTESI_EXPERIMENTAL_SUNODO_VALIDATOR_REDIS_ENDPOINT`. +- Logged server-manager tainted session errors on advance-runner and inspect-server. +- Adjusted dispatcher, advance-runner and authority-claimer logs. ## [1.5.0] 2024-07-22 diff --git a/offchain/Cargo.lock b/offchain/Cargo.lock index 9f990e30f..f23b33966 100644 --- a/offchain/Cargo.lock +++ b/offchain/Cargo.lock @@ -242,7 +242,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "advance-runner" -version = "1.5.0" +version = "1.5.1" dependencies = [ "backoff", "clap", @@ -250,7 +250,7 @@ dependencies = [ "grpc-interfaces", "hex", "http-health-check", - "log 1.5.0", + "log 1.5.1", "rand 0.8.5", "rollups-events", "sha3", @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "authority-claimer" -version = "1.5.0" +version = "1.5.1" dependencies = [ "async-trait", "backoff", @@ -478,7 +478,7 @@ dependencies = [ "ethers", "ethers-signers", "http-server", - "log 1.5.0", + "log 1.5.1", "redacted", "rollups-events", "rusoto_core", @@ -1205,7 +1205,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "contracts" -version = "1.5.0" +version = "1.5.1" dependencies = [ "eth-state-fold-types", "snafu 0.8.2", @@ -1545,7 +1545,7 @@ dependencies = [ [[package]] name = "dispatcher" -version = "1.5.0" +version = "1.5.1" dependencies = [ "async-trait", "backoff", @@ -1555,7 +1555,7 @@ dependencies = [ "futures", "http-server", "im", - "log 1.5.0", + "log 1.5.1", "rand 0.8.5", "redis", "rollups-events", @@ -2451,7 +2451,7 @@ dependencies = [ [[package]] name = "graphql-server" -version = "1.5.0" +version = "1.5.1" dependencies = [ "actix-cors", "actix-web", @@ -2460,7 +2460,7 @@ dependencies = [ "hex", "http-health-check", "juniper", - "log 1.5.0", + "log 1.5.1", "rollups-data", "serde", "serde_json", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "grpc-interfaces" -version = "1.5.0" +version = "1.5.1" dependencies = [ "prost", "tonic", @@ -2605,7 +2605,7 @@ dependencies = [ [[package]] name = "host-runner" -version = "1.5.0" +version = "1.5.1" dependencies = [ "actix-web", "async-trait", @@ -2616,7 +2616,7 @@ dependencies = [ "grpc-interfaces", "hex", "http-health-check", - "log 1.5.0", + "log 1.5.1", "mockall", "rand 0.8.5", "reqwest 0.12.2", @@ -2690,7 +2690,7 @@ dependencies = [ [[package]] name = "http-health-check" -version = "1.5.0" +version = "1.5.1" dependencies = [ "axum 0.7.5", "snafu 0.8.2", @@ -2700,7 +2700,7 @@ dependencies = [ [[package]] name = "http-server" -version = "1.5.0" +version = "1.5.1" dependencies = [ "axum 0.7.5", "clap", @@ -2954,13 +2954,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexer" -version = "1.5.0" +version = "1.5.1" dependencies = [ "backoff", "clap", "env_logger", "http-health-check", - "log 1.5.0", + "log 1.5.1", "rand 0.8.5", "rollups-data", "rollups-events", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "inspect-server" -version = "1.5.0" +version = "1.5.1" dependencies = [ "actix-cors", "actix-web", @@ -3015,7 +3015,7 @@ dependencies = [ "grpc-interfaces", "hex", "http-health-check", - "log 1.5.0", + "log 1.5.1", "reqwest 0.12.2", "serde", "serial_test", @@ -3283,7 +3283,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "log" -version = "1.5.0" +version = "1.5.1" dependencies = [ "built", "clap", @@ -4154,7 +4154,7 @@ dependencies = [ [[package]] name = "redacted" -version = "1.5.0" +version = "1.5.1" dependencies = [ "url", ] @@ -4420,7 +4420,7 @@ dependencies = [ [[package]] name = "rollups-data" -version = "1.5.0" +version = "1.5.1" dependencies = [ "backoff", "base64 0.22.0", @@ -4441,7 +4441,7 @@ dependencies = [ [[package]] name = "rollups-events" -version = "1.5.0" +version = "1.5.1" dependencies = [ "backoff", "base64 0.22.0", @@ -4464,7 +4464,7 @@ dependencies = [ [[package]] name = "rollups-http-client" -version = "1.5.0" +version = "1.5.1" dependencies = [ "hyper 0.14.28", "serde", @@ -5159,14 +5159,14 @@ dependencies = [ [[package]] name = "state-server" -version = "1.5.0" +version = "1.5.1" dependencies = [ "clap", "eth-block-history", "eth-state-fold", "eth-state-fold-types", "eth-state-server-lib", - "log 1.5.0", + "log 1.5.1", "serde", "snafu 0.8.2", "tokio", @@ -5347,7 +5347,7 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "test-fixtures" -version = "1.5.0" +version = "1.5.1" dependencies = [ "anyhow", "backoff", @@ -5919,7 +5919,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "types" -version = "1.5.0" +version = "1.5.1" dependencies = [ "anyhow", "async-trait", diff --git a/offchain/Cargo.toml b/offchain/Cargo.toml index 23ffb98e5..b930fd0b4 100644 --- a/offchain/Cargo.toml +++ b/offchain/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "1.5.0" +version = "1.5.1" license = "Apache-2.0" edition = "2021" diff --git a/offchain/advance-runner/src/runner.rs b/offchain/advance-runner/src/runner.rs index 69b7e89c5..f2a72d88c 100644 --- a/offchain/advance-runner/src/runner.rs +++ b/offchain/advance-runner/src/runner.rs @@ -127,14 +127,14 @@ impl Runner { .context(ProduceOutputsSnafu)?; tracing::trace!("produced outputs in broker stream"); - let dapp_address = rollups_claim.dapp_address.clone(); + let claim = rollups_claim.clone(); self.broker .produce_rollups_claim(rollups_claim) .await .context(ProduceClaimSnafu)?; tracing::info!( - "produced epoch claim in broker stream for dapp address {:?}", - dapp_address + "produced epoch claim in broker stream: {:?}", + claim ); } Err(source) => { diff --git a/offchain/advance-runner/src/server_manager/facade.rs b/offchain/advance-runner/src/server_manager/facade.rs index c37375635..ce03dc7ba 100644 --- a/offchain/advance-runner/src/server_manager/facade.rs +++ b/offchain/advance-runner/src/server_manager/facade.rs @@ -180,7 +180,7 @@ impl ServerManagerFacade { ) -> Result> { tracing::trace!("sending advance-state input to server-manager"); - grpc_call!(self, advance_state, { + let response = grpc_call!(self, advance_state, { let input_metadata = InputMetadata { msg_sender: Some(Address { data: (*input_metadata.msg_sender.inner()).into(), @@ -197,7 +197,9 @@ impl ServerManagerFacade { input_metadata: Some(input_metadata), input_payload: input_payload.clone(), } - })?; + }); + + self.handle_advance_state_error(response).await?; tracing::trace!("waiting until the input is processed"); @@ -271,6 +273,77 @@ impl ServerManagerFacade { Ok(outputs) } + /// Handle response to advance-state request + #[tracing::instrument(level = "trace", skip_all)] + async fn handle_advance_state_error( + &mut self, + response: Result, + ) -> Result<()> { + if response.is_ok() { + return Ok(()); + } + + // Capture server-manager error for advance-state request + // to try to get the reason for a possible tainted session error. + let err = response.unwrap_err(); + + let err: ServerManagerError = match err { + ServerManagerError::MethodCallError { + method, + request_id, + source, + } => { + // Original error message to be reported by default. + let mut message = source.message().to_string(); + // The server-manager signals with code Dataloss when the session has been previously tainted. + if source.code() == tonic::Code::DataLoss { + // Recover the tainted session reason from the session's status. + let status_response = grpc_call!( + self, + get_session_status, + GetSessionStatusRequest { + session_id: self.config.session_id.clone(), + } + ); + + if status_response.is_err() { + match status_response.unwrap_err() { + ServerManagerError::MethodCallError { + method: _, + request_id: _, + source, + } => { + tracing::error!( + "get-session-status error: {:?}", + source.message() + ); + } + _ => (), + } + } else { + let status_response = status_response.unwrap(); + let taint_status = status_response.taint_status.clone(); + if let Some(taint_status) = taint_status { + message = format!( + "Server manager session was tainted: {} ({})", + taint_status.error_code, + taint_status.error_message + ); + tracing::error!(message); + } + } + } + ServerManagerError::MethodCallError { + method, + request_id, + source: tonic::Status::new(source.code(), message), + } + } + _ => err, + }; + Err(err) + } + /// Send a finish-epoch request to the server-manager /// Return the epoch claim and the proofs #[tracing::instrument(level = "trace", skip_all)] @@ -369,3 +442,15 @@ impl ServerManagerFacade { Err(ServerManagerError::PendingInputsExceededError {}) } } + +#[tracing::instrument(level = "trace", skip_all)] +fn get_advance_state_error_message(err: &ServerManagerError) -> Option { + match err { + ServerManagerError::MethodCallError { + method: _, + request_id: _, + source, + } => Some(source.message().to_string()), + _ => None, + } +} diff --git a/offchain/authority-claimer/README.md b/offchain/authority-claimer/README.md index 068689fac..665231cfa 100644 --- a/offchain/authority-claimer/README.md +++ b/offchain/authority-claimer/README.md @@ -17,13 +17,22 @@ All dapps must share the same History contract and the same chain ID. Instead of using evironment variables, the claimer will get the list of application addresses from Redis, through the `experimental-dapp-addresses-config` key. -This key holds a Redis Set value. -You must use commands such as SADD and SREM to manipulate the list of addresses. -Addresses are encoded as hex strings without the leading `"0x"`. -Redis values are case sensitive, so addresses must be in lowercase format. -Example address value: `"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"`. +This key holds a [Redis Set](https://redis.io/docs/latest/develop/data-types/sets/) value. +You must use commands such as [`SADD`](https://redis.io/docs/latest/commands/sadd/) and [`SREM`]https://redis.io/docs/latest/commands/srem/() to manipulate the set of addresses. -You may rewrite the list of addresses at any time, +Application addresses must be encoded as hex strings. +The prefix `0x` is ignored by the `authority-claimer` when loading applications adresses. +However, it's advised to use it as a matter of convention. +Example address value: `"0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"`. + +> [!NOTE] +> Duplicate addresses as well as malformed addresses are detected and logged. + +> [!TIP] +> Application addresses are case insensitive even though Redis values are not. +> So, even though `"0x00000000000000000000000000000000deadbeef"` and `"0x00000000000000000000000000000000DeadBeef"` may belong to the same Redis Set, they are considered to be the same application address and one of them will be identified as a duplicate. + +You may update the contents of `experimental-dapp-addresses-config` at any time, the claimer will adjust accordingly. -The list of addresses can be empty at any time, - the claimer will wait until an application address is added to the set to resume operations. +The set of addresses may be emptied at any time as well, + the claimer will wait until an application address is added to the set before resuming operations. diff --git a/offchain/authority-claimer/src/claimer.rs b/offchain/authority-claimer/src/claimer.rs index ae9ec7b9c..2119c3ed5 100644 --- a/offchain/authority-claimer/src/claimer.rs +++ b/offchain/authority-claimer/src/claimer.rs @@ -96,7 +96,7 @@ where .listen() .await .context(BrokerListenerSnafu)?; - debug!("Got a claim from the broker: {:?}", rollups_claim); + info!("Received claim from the broker: {:?}", rollups_claim); let is_duplicated_rollups_claim = self .duplicate_checker @@ -108,7 +108,7 @@ where continue; } - info!("Sending a new rollups claim"); + info!("Sending a new rollups claim transaction"); self.transaction_sender = self .transaction_sender .send_rollups_claim_transaction(rollups_claim) diff --git a/offchain/authority-claimer/src/listener.rs b/offchain/authority-claimer/src/listener.rs index 992611908..9ab334d0f 100644 --- a/offchain/authority-claimer/src/listener.rs +++ b/offchain/authority-claimer/src/listener.rs @@ -7,7 +7,7 @@ use rollups_events::{ RollupsClaimsStream, INITIAL_ID, }; use snafu::ResultExt; -use std::{collections::HashMap, fmt::Debug}; +use std::{collections::HashMap, collections::HashSet, fmt::Debug}; /// The `BrokerListener` listens for new claims from the broker. #[async_trait] @@ -41,7 +41,7 @@ impl DefaultBrokerListener { chain_id: u64, dapp_address: Address, ) -> Result { - tracing::trace!("Connecting to the broker ({:?})", broker_config); + tracing::info!("Connecting to the broker ({:?})", broker_config); let broker = Broker::new(broker_config).await?; let dapp_metadata = DAppMetadata { chain_id, @@ -94,7 +94,7 @@ impl MultidappBrokerListener { broker_config: BrokerConfig, chain_id: u64, ) -> Result { - tracing::trace!( + tracing::info!( "Connecting to the broker ({:?}) on multidapp mode", broker_config ); @@ -119,11 +119,22 @@ impl MultidappBrokerListener { // Gets the dapps from the broker. let dapps = self.broker.get_dapps().await.context(BrokerSnafu)?; assert!(!dapps.is_empty()); - tracing::info!( - "Got the following dapps from key \"{}\": {:?}", - rollups_events::DAPPS_KEY, - dapps - ); + { + // Logging if the dapps changed. + let old_dapps: HashSet
= self + .streams + .iter() + .map(|(stream, _)| stream.dapp_address.clone()) + .collect(); + let new_dapps = HashSet::from_iter(dapps.clone()); + if old_dapps != new_dapps { + tracing::info!( + "Updated list of dapp addresses from key \"{}\": {:?}", + rollups_events::DAPPS_KEY, + new_dapps + ); + } + } // Converts dapps to streams. let streams: Vec<_> = dapps @@ -520,35 +531,6 @@ mod tests { assert_listen(&mut listener, &dapps, &vec![0]).await; } - #[tokio::test] - async fn multidapp_listen_with_duplicate_dapps() { - let docker = Cli::default(); - let (fixture, mut listener, dapps) = - setup_multidapp_listener(&docker, false).await.unwrap(); - fixture.dapps_set(vec![]).await; - - // Initializes with 0 addresses in the set. - assert_eq!(0, fixture.dapps_members().await.len()); - - // We add a lowercase and an uppercase version of the same address. - let dapp: Address = [10; 20].into(); - fixture.dapps_add(dapp.to_string().to_lowercase()).await; - fixture.dapps_add(dapp.to_string().to_uppercase()).await; - - // We now have 2 addresses in the set. - assert_eq!(2, fixture.dapps_members().await.len()); - - // We then produce some claims and listen for them. - let mut epochs = vec![0; dapps.len()]; - let indexes = vec![2, 2, 0]; - multidapp_produce_claims(&fixture, &mut epochs, &dapps, &indexes).await; - let indexes = vec![2, 2]; - assert_listen(&mut listener, &dapps, &indexes).await; - - // Now we have 1 address because one of the duplicates got deleted. - assert_eq!(1, fixture.dapps_members().await.len()); - } - #[tokio::test] async fn multidapp_listen_with_changing_dapps() { let docker = Cli::default(); diff --git a/offchain/authority-claimer/src/sender.rs b/offchain/authority-claimer/src/sender.rs index 428e45574..70997f921 100644 --- a/offchain/authority-claimer/src/sender.rs +++ b/offchain/authority-claimer/src/sender.rs @@ -260,7 +260,7 @@ impl TransactionSender for DefaultTransactionSender { dapp_address, }) .inc(); - trace!("Claim transaction confirmed: `{:?}`", receipt); + info!("Claim transaction confirmed: `{:?}`", receipt); Ok(Self { tx_manager, ..self }) } diff --git a/offchain/dispatcher/src/dispatcher.rs b/offchain/dispatcher/src/dispatcher.rs index da5693ae8..9246f8b20 100644 --- a/offchain/dispatcher/src/dispatcher.rs +++ b/offchain/dispatcher/src/dispatcher.rs @@ -87,7 +87,6 @@ pub async fn start( ) .await?; - trace!("Starting dispatcher..."); loop { match block_subscription.next().await { Some(Ok(BlockStreamItem::NewBlock(b))) => { diff --git a/offchain/dispatcher/src/drivers/context.rs b/offchain/dispatcher/src/drivers/context.rs index 04180ea5a..3ce10f54d 100644 --- a/offchain/dispatcher/src/drivers/context.rs +++ b/offchain/dispatcher/src/drivers/context.rs @@ -7,6 +7,7 @@ use crate::{ }; use rollups_events::DAppMetadata; +use tracing::info; use types::foldables::Input; #[derive(Debug)] @@ -134,7 +135,9 @@ impl Context { (Some(_), None) => true, // Consider input_epoch greater than None (None, _) => false, // None is never greater than any value }, - "Assertion failed: last_input_epoch should be greater than last_finished_epoch" + "cannot finish epoch: last_input_epoch ({:?}) is not greater than last_finished_epoch ({:?})", + self.last_input_epoch, + self.last_finished_epoch ); broker.finish_epoch(self.inputs_sent).await?; @@ -144,6 +147,10 @@ impl Context { .inc(); self.last_finished_epoch = self.last_input_epoch; + info!( + "sent finish_epoch event for epoch {:?}", + self.last_finished_epoch + ); Ok(()) } } diff --git a/offchain/dispatcher/src/drivers/machine.rs b/offchain/dispatcher/src/drivers/machine.rs index ecab9a3d8..c0e9b1213 100644 --- a/offchain/dispatcher/src/drivers/machine.rs +++ b/offchain/dispatcher/src/drivers/machine.rs @@ -37,7 +37,7 @@ impl MachineDriver { }; let block_number = block.number.as_u64(); - tracing::debug!("reacting to standalone block {}", block_number); + tracing::info!("received block {}", block_number); context.finish_epoch_if_needed(block_number, broker).await?; Ok(()) @@ -53,7 +53,7 @@ impl MachineDriver { broker: &impl BrokerSend, ) -> Result<(), BrokerFacadeError> { tracing::trace!( - "Last input sent to machine manager `{}`, current input `{}`", + "Last input sent to advance-runner: `{}`; current input: `{}`", context.inputs_sent(), dapp_input_box.inputs.len() ); diff --git a/offchain/inspect-server/src/inspect.rs b/offchain/inspect-server/src/inspect.rs index de5665cae..8948d5c0d 100644 --- a/offchain/inspect-server/src/inspect.rs +++ b/offchain/inspect-server/src/inspect.rs @@ -1,15 +1,19 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -use tokio::sync::{mpsc, oneshot}; -use tonic::Request; +use tokio::sync::{ + mpsc, + oneshot::{self}, +}; +use tonic::{transport::Channel, Code, Request, Response, Status}; use uuid::Uuid; use crate::config::InspectServerConfig; use crate::error::InspectError; use grpc_interfaces::cartesi_server_manager::{ - server_manager_client::ServerManagerClient, InspectStateRequest, + server_manager_client::ServerManagerClient, GetSessionStatusRequest, + GetSessionStatusResponse, InspectStateRequest, }; pub use grpc_interfaces::cartesi_server_manager::{ CompletionStatus, InspectStateResponse, Report, @@ -66,7 +70,7 @@ fn respond( } } -/// Loop that answers requests comming from inspect_rx. +/// Loop that answers requests coming from inspect_rx. async fn handle_inspect( address: String, session_id: String, @@ -99,17 +103,72 @@ async fn handle_inspect( grpc_request .metadata_mut() .insert("request-id", request_id.parse().unwrap()); - let grpc_response = client.inspect_state(grpc_request).await; + let inspect_response = client.inspect_state(grpc_request).await; - tracing::debug!("got grpc response from inspect_state response={:?} request_id={}", grpc_response, request_id); + tracing::debug!("got grpc response from inspect_state response={:?} request_id={}", + inspect_response, request_id); - let response = grpc_response - .map(|result| result.into_inner()) - .map_err(|e| InspectError::InspectFailed { - message: e.message().to_string(), - }); + let response = if inspect_response.is_ok() { + Ok(inspect_response.unwrap().into_inner()) + } else { + // The server-manager does not inform the session tainted reason. + // Trying to get it from the session's status. + let message = handle_inspect_error( + inspect_response.unwrap_err(), + session_id.clone(), + &mut client, + ) + .await; + Err(InspectError::InspectFailed { message }) + }; respond(request.response_tx, response); } } } } + +async fn get_session_status( + session_id: String, + client: &mut ServerManagerClient, +) -> Result, Status> { + let session_id = session_id.clone(); + let mut status_request = + Request::new(GetSessionStatusRequest { session_id }); + let request_id = Uuid::new_v4().to_string(); + status_request + .metadata_mut() + .insert("request-id", request_id.parse().unwrap()); + client.get_session_status(status_request).await +} + +async fn handle_inspect_error( + status: Status, + session_id: String, + client: &mut ServerManagerClient, +) -> String { + let mut message = status.message().to_string(); + + // If the session was previously tainted, the server-manager replies it with code DataLoss. + // Trying to recover the reason for the session tainted from the session's status. + // If not available, we return the original status error message. + if status.code() == Code::DataLoss { + let status_response = get_session_status(session_id, client).await; + if status_response.is_err() { + let err = status_response.unwrap_err().message().to_string(); + tracing::error!("get-session-status error: {:?}", err); + } else { + let status_response = status_response.unwrap(); + let status_response = status_response.get_ref(); + let taint_status = status_response.taint_status.clone(); + if let Some(taint_status) = taint_status { + message = format!( + "Server manager session was tainted: {} ({})", + taint_status.error_code, taint_status.error_message + ); + tracing::error!(message); + } + } + } + + message +} diff --git a/offchain/rollups-events/src/broker/mod.rs b/offchain/rollups-events/src/broker/mod.rs index 529d1d1ee..1883f46fd 100644 --- a/offchain/rollups-events/src/broker/mod.rs +++ b/offchain/rollups-events/src/broker/mod.rs @@ -376,12 +376,23 @@ impl Broker { let mut dapp_addresses: Vec
= vec![]; for value in reply { let normalized = value.to_lowercase(); - let dapp_address = Address::from_str(&normalized).unwrap(); - if dapp_addresses.contains(&dapp_address) { - let _: () = - self.connection.clone().srem(DAPPS_KEY, value).await?; - } else { - dapp_addresses.push(dapp_address); + let dapp_address = Address::from_str(&normalized); + match dapp_address { + Ok(dapp_address) => { + if dapp_addresses.contains(&dapp_address) { + tracing::info!( + "Ignored duplicate DApp address {:?}", + value, + ) + } else { + dapp_addresses.push(dapp_address); + } + } + Err(message) => tracing::info!( + "Error while parsing DApp address {:?}: {}", + normalized, + message, + ), } }