Skip to content

feat: check AML risks#1134

Draft
Dodecahedr0x wants to merge 1 commit intomasterfrom
dode/aml
Draft

feat: check AML risks#1134
Dodecahedr0x wants to merge 1 commit intomasterfrom
dode/aml

Conversation

@Dodecahedr0x
Copy link
Copy Markdown
Contributor

@Dodecahedr0x Dodecahedr0x commented Apr 6, 2026

Summary

  • Uses Range API to ensure that delegation action signers are not risky

Compatibility

No breaking change, but new optional config fields:

# ------------------------------------------------------------------------------
# Optional: Range Risk API validation for post-delegation actions
# ------------------------------------------------------------------------------
# When enabled, all addresses referenced by post-delegation actions are checked
# against Range API before actions are allowed to execute.
[chainlink.risk]
# Enables/disables Range risk checks.
# Default: false
# Env: MBV_CHAINLINK__RISK__ENABLED
enabled = false

# Range API base URL.
# Default: "https://api.range.org/v1"
# Env: MBV_CHAINLINK__RISK__BASE_URL
base-url = "https://api.range.org/v1"

# Range API bearer token.
# Default: None
# Env: MBV_CHAINLINK__RISK__API_KEY
api-key = "your-api-key"

# Cache TTL.
# Default: "30 days"
# Env: MBV_CHAINLINK__RISK__CACHE_TTL
cache-ttl = "30 days"

# HTTP timeout for Range API calls.
# Default: "5s"
# Env: MBV_CHAINLINK__RISK__REQUEST_TIMEOUT
request-timeout = "5s"

# Risk score threshold to consider an address high risk, on scale of 0-10.
# Default: 5
# Env: MBV_CHAINLINK__RISK__RISK_SCORE_THRESHOLD
# risk-score-threshold = 5

Summary by CodeRabbit

Release Notes

  • New Features

    • Added optional address risk assessment capability for delegation actions through integration with external risk evaluation service.
  • Configuration

    • New [chainlink.risk] configuration block with toggles for risk checks, API endpoints, caching parameters, and risk thresholds.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Manual Deploy Available

You can trigger a manual deploy of this PR branch to testnet:

Deploy to Testnet 🚀

Alternative: Comment /deploy on this PR to trigger deployment directly.

⚠️ Note: Manual deploy requires authorization. Only authorized users can trigger deployments.

Comment updated automatically when the PR is synchronized.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new magicblock-aml Rust crate implementing an address risk-assessment service that queries a Range API and caches results in SQLite. The workspace configuration is extended to include this crate, and the Chainlink component is modified to integrate the new risk service for validating delegation action addresses during post-delegation checks. Configuration handling is updated to support the new risk settings including base URL, API key, cache TTL, timeout, and score threshold. Tests are updated to accommodate the new risk\_service parameter throughout the codebase.

Suggested reviewers

  • GabrielePicco
  • thlorenz
  • snawaz
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dode/aml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs (1)

246-255: 🧹 Nitpick | 🔵 Trivial

Add at least one FetchCloner::new(..., Some(risk_service)) test path.

These updates correctly match the new signature, but all touched call sites keep risk_service as None, so this file still doesn’t exercise AML-gated behavior directly.

Also applies to: 1763-1772, 1837-1846, 1910-1919

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs` around lines 246 -
255, Add a test path that passes Some(risk_service) into FetchCloner::new so
AML-gated behavior is exercised: create a lightweight mock or stub that
implements the same trait/interface the production Risk service uses (e.g., a
MockRiskService or TestRiskService), instantiate it and pass
Some(mock_risk_service) into FetchCloner::new in at least one of the tests that
currently pass None, then assert the expected AML-related behavior (e.g.,
transactions blocked/filtered or specific error/results) to prove the gate
works; apply the same change to the other FetchCloner::new call sites in this
test file that still use None.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config.example.toml`:
- Line 272: Replace the misleading literal value for the config key api-key with
a clearly non-sensitive placeholder or comment it out so users won't mistakenly
paste real credentials; locate the api-key entry (symbol "api-key") in the
example config and either prefix it with a comment marker or change the value to
an obvious placeholder like "<REPLACE_WITH_YOUR_API_KEY>" or
"YOUR_API_KEY_HERE".
- Around line 259-261: The env var prefixes in the [chainlink.risk] section are
incorrect: replace occurrences of MBV_CHAINLINK__RANGE_RISK__* with
MBV_CHAINLINK__RISK__* (e.g., update the comment lines that currently reference
MBV_CHAINLINK__RANGE_RISK__ENABLED and the other entries in the same section) so
they match the TOML section name and the test expectations; apply the same
replacement to all other env var comments in that section (the subsequent
entries referenced in the comment block).

In `@magicblock-aml/src/lib.rs`:
- Around line 167-179: The is_high_risk logic in assessment_from_value is
inverted: change the comparison so addresses with risk_score >=
self.risk_score_threshold are marked high risk. Update the construction of
AddressRiskAssessment in assessment_from_value to set is_high_risk based on
self.risk_score_threshold <= score (i.e. score meets or exceeds the threshold)
so it matches the threshold semantics and the behavior used elsewhere (e.g., the
earlier cache/write logic).
- Around line 73-79: The CREATE TABLE for address_risk_cache is invalid because
the PRIMARY KEY references network which is not declared; fix by making the
schema and all SQL usages consistent: add a network TEXT NOT NULL column to the
address_risk_cache definition (alongside address, risk_score, fetched_at_unix_s)
and keep PRIMARY KEY (network, address), then update the related
INSERT/UPSERT/ON CONFLICT statements (the SQL at the locations around lines
127-130 and 155-161) to target the same conflict key (network, address);
alternatively, if you intend a Solana-only cache, remove network from the
PRIMARY KEY and change those ON CONFLICT targets to just address—ensure
address_risk_cache, the columns (address, network, risk_score,
fetched_at_unix_s), and the ON CONFLICT keys are all consistent.
- Around line 45-52: The SQLite operations in read_cache and write_cache are
blocking and currently run on the async executor inside check_address; change
those DB accesses to run on a blocking thread: make read_cache and write_cache
async helpers that call tokio::task::spawn_blocking (or
tokio::runtime::Handle::spawn_blocking) and perform all rusqlite queries inside
the spawned closure. Update RiskService so the Connection is not locked on the
async runtime (replace the parking_lot::Mutex<Connection> usage by either
storing a DB path/handle and opening/using the Connection inside spawn_blocking,
or keep the Connection behind a sync-only lock used only inside the blocking
closure), and call await on the spawn_blocking join handle from check_address;
ensure function signatures for read_cache and write_cache are updated
accordingly and reference these symbols (RiskService, check_address, read_cache,
write_cache).
- Around line 74-76: The CREATE TABLE schema for address_risk_cache uses
risk_score REAL but the code writes and reads u64, so update the SQL in the
create statement to declare risk_score INTEGER NOT NULL; ensure any
insert/update that uses params![address, assessment.risk_score, ...] keeps
passing an integer type and the retrieval that does let risk_score: u64 =
row.get(0)? will then match the INTEGER column type; adjust the CREATE TABLE
string surrounding address_risk_cache and verify any related queries reference
the same column type.

In `@magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs`:
- Around line 530-545: The extraction of signer addresses from
delegation_actions is verbose; simplify the nested filter_map/if by using
filter_map with a conditional expression so it directly returns the pubkey when
meta.is_signer is true. Replace the inner closure in the flat_map over
delegation_actions.iter() (which currently uses
instruction.accounts.iter().filter_map(|meta| { if meta.is_signer {
Some(meta.pubkey) } else { None } })) with a concise form such as
instruction.accounts.iter().filter_map(|meta| meta.is_signer.then(||
meta.pubkey)) and keep collecting into the BTreeSet as before.
- Around line 522-559: The validation currently calls risk_service.check_address
sequentially inside validate_post_delegation_action_addresses which can be slow;
change it to run the checks concurrently by collecting the unique addresses
(from the addresses BTreeSet) into a Vec and then spawning async tasks (e.g.,
with futures::future::try_join_all or a bounded stream like
futures::stream::FuturesUnordered with a concurrency limit) to call
risk_service.check_address(&addr.to_string()).await for each address, then
inspect the returned assessments and return the same
ChainlinkError::RiskyDelegationActionAddress if any assessment.is_high_risk
(preserving address and risk_score) otherwise return Ok(()). Ensure you await
the combined future and propagate the first error or convert high-risk
assessments into an Err as before.

In `@magicblock-chainlink/src/chainlink/mod.rs`:
- Around line 142-146: The call to RiskService::try_from_config is passing
&ledger_path which produces a &&Path unnecessarily; change the invocation to
pass ledger_path directly (i.e.,
RiskService::try_from_config(&chainlink_config.risk, ledger_path)?) so you pass
a &Path instead of a double reference, then keep the subsequent .map(Arc::new)
unchanged; locate this in the block constructing risk_service that references
chainlink_config.risk, ledger_path, and RiskService::try_from_config.

---

Outside diff comments:
In `@magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs`:
- Around line 246-255: Add a test path that passes Some(risk_service) into
FetchCloner::new so AML-gated behavior is exercised: create a lightweight mock
or stub that implements the same trait/interface the production Risk service
uses (e.g., a MockRiskService or TestRiskService), instantiate it and pass
Some(mock_risk_service) into FetchCloner::new in at least one of the tests that
currently pass None, then assert the expected AML-related behavior (e.g.,
transactions blocked/filtered or specific error/results) to prove the gate
works; apply the same change to the other FetchCloner::new call sites in this
test file that still use None.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d681ffca-6dbe-4551-950e-dcbac7e31975

📥 Commits

Reviewing files that changed from the base of the PR and between b9d2ada and bfb045a.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • test-integration/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • Cargo.toml
  • config.example.toml
  • magicblock-aml/Cargo.toml
  • magicblock-aml/src/lib.rs
  • magicblock-api/src/magic_validator.rs
  • magicblock-chainlink/Cargo.toml
  • magicblock-chainlink/src/chainlink/errors.rs
  • magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs
  • magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs
  • magicblock-chainlink/src/chainlink/mod.rs
  • magicblock-chainlink/tests/utils/test_context.rs
  • magicblock-config/src/config/chain.rs
  • magicblock-config/src/config/mod.rs
  • magicblock-config/src/consts.rs
  • magicblock-config/src/tests.rs
  • test-integration/test-chainlink/src/ixtest_context.rs
  • test-integration/test-chainlink/src/test_context.rs

Comment on lines +259 to +261
# Enables/disables Range risk checks.
# Default: false
# Env: MBV_CHAINLINK__RANGE_RISK__ENABLED
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Environment variable prefix in comments is incorrect.

The comments reference MBV_CHAINLINK__RANGE_RISK__* but based on the TOML section name [chainlink.risk] and the test assertions in magicblock-config/src/tests.rs (which use MBV_CHAINLINK__RISK__*), the correct prefix should be MBV_CHAINLINK__RISK__*.

📝 Proposed fix
 [chainlink.risk]
 # Enables/disables Range risk checks.
 # Default: false
-# Env: MBV_CHAINLINK__RANGE_RISK__ENABLED
+# Env: MBV_CHAINLINK__RISK__ENABLED
 enabled = false

Apply the same fix to all other env var references in this section (lines 266, 271, 276, 281, 286).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Enables/disables Range risk checks.
# Default: false
# Env: MBV_CHAINLINK__RANGE_RISK__ENABLED
# Enables/disables Range risk checks.
# Default: false
# Env: MBV_CHAINLINK__RISK__ENABLED
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config.example.toml` around lines 259 - 261, The env var prefixes in the
[chainlink.risk] section are incorrect: replace occurrences of
MBV_CHAINLINK__RANGE_RISK__* with MBV_CHAINLINK__RISK__* (e.g., update the
comment lines that currently reference MBV_CHAINLINK__RANGE_RISK__ENABLED and
the other entries in the same section) so they match the TOML section name and
the test expectations; apply the same replacement to all other env var comments
in that section (the subsequent entries referenced in the comment block).

Comment on lines +45 to +52
pub struct RiskService {
client: Client,
cache: Mutex<Connection>,
base_url: String,
api_key: String,
cache_ttl: Duration,
risk_score_threshold: u64,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "lib.rs" | grep magicblock

Repository: magicblock-labs/magicblock-validator

Length of output: 932


🏁 Script executed:

head -n 170 magicblock-aml/src/lib.rs | tail -n 130

Repository: magicblock-labs/magicblock-validator

Length of output: 3903


🏁 Script executed:

cat -n magicblock-aml/src/lib.rs | sed -n '40,175p'

Repository: magicblock-labs/magicblock-validator

Length of output: 5024


🏁 Script executed:

rg "spawn_blocking" magicblock-aml/src/lib.rs

Repository: magicblock-labs/magicblock-validator

Length of output: 62


Move the SQLite cache work off the async executor.

check_address() is on the async path, but read_cache and write_cache perform blocking rusqlite I/O operations (lines 126-132 and 154-163) under a parking_lot::Mutex on the runtime thread. This blocks the executor and can stall Tokio workers under load. Move DB access behind spawn_blocking or use an async SQLite wrapper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-aml/src/lib.rs` around lines 45 - 52, The SQLite operations in
read_cache and write_cache are blocking and currently run on the async executor
inside check_address; change those DB accesses to run on a blocking thread: make
read_cache and write_cache async helpers that call tokio::task::spawn_blocking
(or tokio::runtime::Handle::spawn_blocking) and perform all rusqlite queries
inside the spawned closure. Update RiskService so the Connection is not locked
on the async runtime (replace the parking_lot::Mutex<Connection> usage by either
storing a DB path/handle and opening/using the Connection inside spawn_blocking,
or keep the Connection behind a sync-only lock used only inside the blocking
closure), and call await on the spawn_blocking join handle from check_address;
ensure function signatures for read_cache and write_cache are updated
accordingly and reference these symbols (RiskService, check_address, read_cache,
write_cache).

Comment on lines +73 to +79
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS address_risk_cache (
address TEXT NOT NULL,
risk_score REAL,
fetched_at_unix_s INTEGER NOT NULL,
PRIMARY KEY (network, address)
)",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Make the cache key/schema internally consistent.

Line 78 references network in the primary key, but the table never declares that column, so CREATE TABLE fails when risk checks are enabled. If network is intentional, Line 130 and Lines 159-161 also need to key by it; otherwise make address the only primary key/conflict target.

Possible minimal fix for a Solana-only cache
         conn.execute_batch(
             "CREATE TABLE IF NOT EXISTS address_risk_cache (
                 address TEXT NOT NULL,
                 risk_score REAL,
                 fetched_at_unix_s INTEGER NOT NULL,
-                PRIMARY KEY (network, address)
+                PRIMARY KEY (address)
             )",
         )?;

Also applies to: 127-130, 155-161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-aml/src/lib.rs` around lines 73 - 79, The CREATE TABLE for
address_risk_cache is invalid because the PRIMARY KEY references network which
is not declared; fix by making the schema and all SQL usages consistent: add a
network TEXT NOT NULL column to the address_risk_cache definition (alongside
address, risk_score, fetched_at_unix_s) and keep PRIMARY KEY (network, address),
then update the related INSERT/UPSERT/ON CONFLICT statements (the SQL at the
locations around lines 127-130 and 155-161) to target the same conflict key
(network, address); alternatively, if you intend a Solana-only cache, remove
network from the PRIMARY KEY and change those ON CONFLICT targets to just
address—ensure address_risk_cache, the columns (address, network, risk_score,
fetched_at_unix_s), and the ON CONFLICT keys are all consistent.

Comment on lines +74 to +76
"CREATE TABLE IF NOT EXISTS address_risk_cache (
address TEXT NOT NULL,
risk_score REAL,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd magicblock-aml && cat -n src/lib.rs | sed -n '70,80p'

Repository: magicblock-labs/magicblock-validator

Length of output: 495


🏁 Script executed:

cd magicblock-aml && cat -n src/lib.rs | sed -n '138,150p'

Repository: magicblock-labs/magicblock-validator

Length of output: 492


🏁 Script executed:

cd magicblock-aml && wc -l src/lib.rs

Repository: magicblock-labs/magicblock-validator

Length of output: 94


🏁 Script executed:

cd magicblock-aml && rg -n 'unwrap\(\)|expect\(' src/lib.rs

Repository: magicblock-labs/magicblock-validator

Length of output: 62


🏁 Script executed:

cd magicblock-aml && sed -n '125,150p' src/lib.rs

Repository: magicblock-labs/magicblock-validator

Length of output: 846


🏁 Script executed:

cd magicblock-aml && sed -n '149,170p' src/lib.rs

Repository: magicblock-labs/magicblock-validator

Length of output: 785


🏁 Script executed:

cd magicblock-aml && sed -n '145,180p' src/lib.rs

Repository: magicblock-labs/magicblock-validator

Length of output: 1119


Change risk_score column from REAL to INTEGER NOT NULL in the schema.

The code writes u64 values (line 155: params![address, assessment.risk_score, ...]) to a REAL column (line 76) but attempts to read them back as u64 (line 142: let risk_score: u64 = row.get(0)?). This type mismatch will cause InvalidColumnType errors at runtime on cache hits, since rusqlite strictly validates column type compatibility.

Schema fix
-                risk_score REAL,
+                risk_score INTEGER NOT NULL,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"CREATE TABLE IF NOT EXISTS address_risk_cache (
address TEXT NOT NULL,
risk_score REAL,
"CREATE TABLE IF NOT EXISTS address_risk_cache (
address TEXT NOT NULL,
risk_score INTEGER NOT NULL,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-aml/src/lib.rs` around lines 74 - 76, The CREATE TABLE schema for
address_risk_cache uses risk_score REAL but the code writes and reads u64, so
update the SQL in the create statement to declare risk_score INTEGER NOT NULL;
ensure any insert/update that uses params![address, assessment.risk_score, ...]
keeps passing an integer type and the retrieval that does let risk_score: u64 =
row.get(0)? will then match the INTEGER column type; adjust the CREATE TABLE
string surrounding address_risk_cache and verify any related queries reference
the same column type.

Comment on lines +167 to +179
fn assessment_from_value(
&self,
value: &Value,
) -> RiskResult<AddressRiskAssessment> {
let score = value
.get("riskScore")
.and_then(|v| v.as_u64())
.ok_or(RiskError::RiskScoreNotFound)?;

Ok(AddressRiskAssessment {
is_high_risk: self.risk_score_threshold >= score,
risk_score: score,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invert the high-risk comparison here.

risk-score-threshold is documented as the cutoff for high-risk addresses, but this code marks scores below the threshold as risky. That also disagrees with Line 144, so the same address can flip classification after its first cache write.

Suggested fix
         Ok(AddressRiskAssessment {
-            is_high_risk: self.risk_score_threshold >= score,
+            is_high_risk: score >= self.risk_score_threshold,
             risk_score: score,
         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn assessment_from_value(
&self,
value: &Value,
) -> RiskResult<AddressRiskAssessment> {
let score = value
.get("riskScore")
.and_then(|v| v.as_u64())
.ok_or(RiskError::RiskScoreNotFound)?;
Ok(AddressRiskAssessment {
is_high_risk: self.risk_score_threshold >= score,
risk_score: score,
})
fn assessment_from_value(
&self,
value: &Value,
) -> RiskResult<AddressRiskAssessment> {
let score = value
.get("riskScore")
.and_then(|v| v.as_u64())
.ok_or(RiskError::RiskScoreNotFound)?;
Ok(AddressRiskAssessment {
is_high_risk: score >= self.risk_score_threshold,
risk_score: score,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-aml/src/lib.rs` around lines 167 - 179, The is_high_risk logic in
assessment_from_value is inverted: change the comparison so addresses with
risk_score >= self.risk_score_threshold are marked high risk. Update the
construction of AddressRiskAssessment in assessment_from_value to set
is_high_risk based on self.risk_score_threshold <= score (i.e. score meets or
exceeds the threshold) so it matches the threshold semantics and the behavior
used elsewhere (e.g., the earlier cache/write logic).

Comment on lines +522 to +559
async fn validate_post_delegation_action_addresses(
&self,
delegation_actions: &DelegationActions,
) -> ChainlinkResult<()> {
let Some(risk_service) = self.risk_service.as_ref() else {
return Ok(());
};

let addresses = delegation_actions
.iter()
.flat_map(|instruction| {
instruction
.accounts
.iter()
.filter_map(|meta| {
if meta.is_signer {
Some(meta.pubkey)
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect::<BTreeSet<_>>();

for address in addresses {
let assessment =
risk_service.check_address(&address.to_string()).await?;
if assessment.is_high_risk {
return Err(ChainlinkError::RiskyDelegationActionAddress {
address,
risk_score: assessment.risk_score,
});
}
}

Ok(())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider parallelizing address risk checks for better performance.

The current implementation checks addresses sequentially. If there are many signer addresses and the risk service has network latency, this could become a bottleneck. Consider using futures::future::try_join_all or similar for parallel checks with a concurrency limit.

That said, the current implementation is correct and simpler. Given that delegation actions typically have few signers, this may be acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs` around lines 522 -
559, The validation currently calls risk_service.check_address sequentially
inside validate_post_delegation_action_addresses which can be slow; change it to
run the checks concurrently by collecting the unique addresses (from the
addresses BTreeSet) into a Vec and then spawning async tasks (e.g., with
futures::future::try_join_all or a bounded stream like
futures::stream::FuturesUnordered with a concurrency limit) to call
risk_service.check_address(&addr.to_string()).await for each address, then
inspect the returned assessments and return the same
ChainlinkError::RiskyDelegationActionAddress if any assessment.is_high_risk
(preserving address and risk_score) otherwise return Ok(()). Ensure you await
the combined future and propagate the first error or convert high-risk
assessments into an Err as before.

Comment on lines +530 to +545
let addresses = delegation_actions
.iter()
.flat_map(|instruction| {
instruction
.accounts
.iter()
.filter_map(|meta| {
if meta.is_signer {
Some(meta.pubkey)
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect::<BTreeSet<_>>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting addresses more concisely.

The nested filter_map with explicit if can be simplified.

📝 Suggested simplification
         let addresses = delegation_actions
             .iter()
             .flat_map(|instruction| {
-                instruction
-                    .accounts
-                    .iter()
-                    .filter_map(|meta| {
-                        if meta.is_signer {
-                            Some(meta.pubkey)
-                        } else {
-                            None
-                        }
-                    })
-                    .collect::<Vec<_>>()
+                instruction.accounts.iter().filter(|meta| meta.is_signer).map(|meta| meta.pubkey)
             })
             .collect::<BTreeSet<_>>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let addresses = delegation_actions
.iter()
.flat_map(|instruction| {
instruction
.accounts
.iter()
.filter_map(|meta| {
if meta.is_signer {
Some(meta.pubkey)
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect::<BTreeSet<_>>();
let addresses = delegation_actions
.iter()
.flat_map(|instruction| {
instruction.accounts.iter().filter(|meta| meta.is_signer).map(|meta| meta.pubkey)
})
.collect::<BTreeSet<_>>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs` around lines 530 -
545, The extraction of signer addresses from delegation_actions is verbose;
simplify the nested filter_map/if by using filter_map with a conditional
expression so it directly returns the pubkey when meta.is_signer is true.
Replace the inner closure in the flat_map over delegation_actions.iter() (which
currently uses instruction.accounts.iter().filter_map(|meta| { if meta.is_signer
{ Some(meta.pubkey) } else { None } })) with a concise form such as
instruction.accounts.iter().filter_map(|meta| meta.is_signer.then(||
meta.pubkey)) and keep collecting into the BTreeSet as before.

Comment on lines +142 to +146
let risk_service = RiskService::try_from_config(
&chainlink_config.risk,
&ledger_path,
)?
.map(Arc::new);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: Redundant reference.

ledger_path is already &Path, so &ledger_path creates &&Path. While this works due to auto-deref, it's cleaner to pass ledger_path directly.

📝 Suggested fix
             let risk_service = RiskService::try_from_config(
                 &chainlink_config.risk,
-                &ledger_path,
+                ledger_path,
             )?
             .map(Arc::new);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let risk_service = RiskService::try_from_config(
&chainlink_config.risk,
&ledger_path,
)?
.map(Arc::new);
let risk_service = RiskService::try_from_config(
&chainlink_config.risk,
ledger_path,
)?
.map(Arc::new);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-chainlink/src/chainlink/mod.rs` around lines 142 - 146, The call
to RiskService::try_from_config is passing &ledger_path which produces a &&Path
unnecessarily; change the invocation to pass ledger_path directly (i.e.,
RiskService::try_from_config(&chainlink_config.risk, ledger_path)?) so you pass
a &Path instead of a double reference, then keep the subsequent .map(Arc::new)
unchanged; locate this in the block constructing risk_service that references
chainlink_config.risk, ledger_path, and RiskService::try_from_config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant