feat: signed context oracle fetch module (Phase 3)#2458
feat: signed context oracle fetch module (Phase 3)#2458hardyjosh wants to merge 14 commits intofeat/signed-context-oracle-discoveryfrom
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughAdds an oracle module and threads oracle-signed context support through metadata parsing, quote generation, candidate construction, and take execution. New async fetch functions retrieve SignedContextV1 from URLs; signed context is propagated into quotes, candidates, take calldata, UI, and tests. Changes
Sequence DiagramsequenceDiagram
participant Client as Requester
participant Raindex as RaindexClient
participant Oracle as Oracle Module
participant Candidates as Candidate Builder
participant Quote as Quote Generator
Client->>Raindex: request quotes/orders
Raindex->>Raindex: extract oracle_url from order meta
alt oracle_url present
Raindex->>Oracle: fetch_signed_context(url)
Oracle-->>Raindex: SignedContextV1 or Error
else no oracle_url or fetch fails
Raindex-->>Raindex: use empty signed_context
end
Raindex->>Candidates: build_candidates_for_order(orders, signed_context)
Candidates->>Oracle: fetch_oracle_for_order(...) (concurrent)
Oracle-->>Candidates: Vec<SignedContextV1>
Candidates-->>Raindex: Vec<TakeOrderCandidate> (includes signed_context)
Raindex->>Quote: get_order_quotes_with_context(..., signed_context)
Quote-->>Raindex: BatchOrderQuotesResponse
Raindex-->>Client: returned quotes
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly Related PRs
Suggested Reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/common/src/raindex_client/take_orders/single_tests.rs (1)
134-145: 🧹 Nitpick | 🔵 TrivialNo tests exercise
execute_single_takewith an actualoracle_url.All test call sites pass
Nonefor the neworacle_urlparameter. Consider adding at least one test with a mocked oracle endpoint (usinghttpmock, which is already a dependency here) to verify thatexecute_single_takecorrectly fetches and threads signed context through to the take-order calldata.
🤖 Fix all issues with AI agents
In `@crates/common/src/lib.rs`:
- Line 10: Add a cfg guard to the oracle module declaration so it is excluded
from WASM builds: wrap the pub mod oracle; line with #[cfg(not(target_family =
"wasm"))] (matching how replays is guarded) because oracle.rs uses
reqwest::Client::builder().timeout(...) which fails when reqwest lacks the wasm
feature; ensure the oracle module is only compiled on non-wasm targets to avoid
compilation errors from raindex_client functions that are exposed to WASM.
In `@crates/common/src/oracle.rs`:
- Around line 44-73: Add URL validation in fetch_signed_context (and ensure
callers like fetch_signed_contexts benefit) by parsing the input with Url::parse
and rejecting non-HTTPS schemes (return OracleError::InvalidUrl). After parsing,
resolve the host to IPs (e.g., via lookup or DNS resolution) and reject
loopback/localhost and private IP ranges (RFC1918/unique local IPv6) before
constructing the reqwest Client and issuing the request; return
OracleError::InvalidUrl on validation failures. Keep the existing behavior of
returning other OracleError variants for network/json failures, and if you
intend to trust oracle URLs, instead document that assumption in the function
comment.
In `@crates/common/src/parsed_meta.rs`:
- Around line 210-265: Tests for oracle parsing
(test_from_meta_item_signed_context_oracle_v1, test_parse_multiple_with_oracle,
test_parse_from_bytes_with_oracle) should use insta snapshots instead of/manual
assertions; update each test to produce a serializable representation (e.g., the
ParsedMeta result or the SignedContextOracleV1 via
SignedContextOracleV1::to_meta_item / ParsedMeta::from_meta_item /
ParsedMeta::parse_multiple / ParsedMeta::parse_from_bytes) and call
insta::assert_yaml_snapshot! (or assert_debug_snapshot!) to record the output,
replacing or augmenting the current assert_eq! / matches! checks so future
metadata changes are captured by snapshots. Ensure the tests import insta and
use stable snapshot names tied to the test function names.
In `@crates/common/src/take_orders/candidates.rs`:
- Around line 94-122: The comment claims per-order work is concurrent but the
code awaits order.get_quotes before calling fetch_oracle_for_order, making them
sequential; fix by starting both futures before awaiting (e.g., create
quotes_fut = order.get_quotes(block_number, gas_string) and signed_context_fut =
fetch_oracle_for_order(order) then await both together with futures::join! or
join to produce (quotes, signed_context)), referencing the futures creation in
the map/async block where order.get_quotes and fetch_oracle_for_order are
called; alternatively, if you prefer the minimal change, update the comment to
remove "concurrently" so it accurately reflects the current sequential behavior.
In `@crates/quote/src/order_quotes.rs`:
- Around line 50-58: The function get_order_quotes_with_context accepts a
Vec<SignedContextV1> but in practice callers only pass a single per-order
context and the API is misleading; change the signature to accept an
Option<SignedContextV1> (or a single SignedContextV1) to make it explicit that
one signed context is applied to all orders, update the doc comment to state
"signed_context is a single signed context applied to all quote targets for all
orders", and adjust any call sites (e.g., where callers fetch per-order context)
to pass the single context or None accordingly; keep SgOrder and SignedContextV1
types unchanged.
| pub mod fuzz; | ||
| pub mod local_db; | ||
| pub mod meta; | ||
| pub mod oracle; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the oracle module
fd -t f oracle.rs crates/common/srcRepository: rainlanguage/rain.orderbook
Length of output: 98
🏁 Script executed:
# Check the main Cargo.toml for reqwest configuration
cat crates/common/Cargo.tomlRepository: rainlanguage/rain.orderbook
Length of output: 2576
🏁 Script executed:
# Look for WASM-related build configurations
rg "wasm" --include="*.toml" --include="*.yml" --include="*.yaml" | head -50Repository: rainlanguage/rain.orderbook
Length of output: 512
🏁 Script executed:
# Read the oracle.rs file to see what it uses
cat -n crates/common/src/oracle.rsRepository: rainlanguage/rain.orderbook
Length of output: 4308
🏁 Script executed:
# Check workspace Cargo.toml for reqwest configuration
fd -t f Cargo.toml | head -5 | xargs grep -l "reqwest" | head -1 | xargs cat | grep -A 10 -B 2 "reqwest"Repository: rainlanguage/rain.orderbook
Length of output: 427
🏁 Script executed:
# Search for WASM builds in CI/github workflows
fd "\.yml\|\.yaml" .github/workflows --exec grep -l "wasm" {} \; 2>/dev/null || echo "No wasm in workflows"Repository: rainlanguage/rain.orderbook
Length of output: 53
🏁 Script executed:
# Check the full lib.rs to see what's exported and how oracle fits
cat -n crates/common/src/lib.rsRepository: rainlanguage/rain.orderbook
Length of output: 972
🏁 Script executed:
# Search for uses of oracle module
rg "oracle" crates/common/src --type rs -nRepository: rainlanguage/rain.orderbook
Length of output: 97
🏁 Script executed:
# Check if there are any WASM-specific build targets or configurations
rg "cdylib\|wasm" Cargo.toml crates/*/Cargo.toml -A 2 -B 2Repository: rainlanguage/rain.orderbook
Length of output: 53
🏁 Script executed:
# Search oracle usage with correct syntax
rg "oracle" crates/common/src -nRepository: rainlanguage/rain.orderbook
Length of output: 5667
🏁 Script executed:
# Check raindex_client to see if other non-guarded modules use wasm-incompatible features
head -50 crates/common/src/raindex_client/mod.rsRepository: rainlanguage/rain.orderbook
Length of output: 1634
🏁 Script executed:
# Search for actual WASM compilation or build targets
fd Cargo.toml | xargs grep -l "wasm32"Repository: rainlanguage/rain.orderbook
Length of output: 55
🏁 Script executed:
# Check what features are actually used when building
rg "wasm_bindgen" crates/common/src --type-listRepository: rainlanguage/rain.orderbook
Length of output: 460
🏁 Script executed:
# Check if the functions that call oracle are exposed/used by WASM
rg "fetch_oracle_for_order|fetch_signed_context" crates/common/src/take_orders/candidates.rs -B 5 -A 5Repository: rainlanguage/rain.orderbook
Length of output: 1272
🏁 Script executed:
# Check if these WASM-exposed raindex_client functions actually call oracle code
rg "pub async fn|pub fn" crates/common/src/raindex_client/order_quotes.rs | head -20Repository: rainlanguage/rain.orderbook
Length of output: 242
🏁 Script executed:
# See if there's any cfg guard around the actual reqwest usage in oracle
rg "Client::builder|timeout" crates/common/src/oracle.rs -B 5 -A 5Repository: rainlanguage/rain.orderbook
Length of output: 526
🏁 Script executed:
# Check if take_orders is part of WASM-exposed APIs
rg "take_orders" crates/common/src/raindex_client/mod.rsRepository: rainlanguage/rain.orderbook
Length of output: 91
🏁 Script executed:
# Check if there are any WASM build tests or targets
rg "wasm32\|wasm_bindgen_test" Cargo.toml .github/ -lRepository: rainlanguage/rain.orderbook
Length of output: 53
🏁 Script executed:
# Check the actual build configuration - does cdylib mean WASM or something else?
rg "crate-type" crates/*/Cargo.toml -B 2 -A 2Repository: rainlanguage/rain.orderbook
Length of output: 593
Add cfg guard to oracle module for WASM compatibility.
The oracle module uses reqwest::Client::builder().timeout(...) (line 49–50 of oracle.rs), which is unavailable on WASM targets when reqwest is not built with the wasm feature. Since the workspace reqwest has only the json feature enabled, this code cannot compile for WASM. The module is publicly exported without guards and is called from WASM-exposed raindex_client functions. Add #[cfg(not(target_family = "wasm"))] to the oracle module declaration (line 10) to prevent this compilation failure, consistent with the pattern used for replays (line 15–16).
🤖 Prompt for AI Agents
In `@crates/common/src/lib.rs` at line 10, Add a cfg guard to the oracle module
declaration so it is excluded from WASM builds: wrap the pub mod oracle; line
with #[cfg(not(target_family = "wasm"))] (matching how replays is guarded)
because oracle.rs uses reqwest::Client::builder().timeout(...) which fails when
reqwest lacks the wasm feature; ensure the oracle module is only compiled on
non-wasm targets to avoid compilation errors from raindex_client functions that
are exposed to WASM.
crates/common/src/oracle.rs
Outdated
| /// Fetch signed context from an oracle endpoint. | ||
| /// | ||
| /// The endpoint must respond to a GET request with a JSON body matching | ||
| /// `OracleResponse` (signer, context, signature). | ||
| pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> { | ||
| let client = Client::builder() | ||
| .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS)) | ||
| .build()?; | ||
|
|
||
| let response: OracleResponse = client | ||
| .get(url) | ||
| .send() | ||
| .await? | ||
| .error_for_status()? | ||
| .json() | ||
| .await?; | ||
|
|
||
| Ok(response.into()) | ||
| } | ||
|
|
||
| /// Fetch signed contexts for multiple oracle URLs concurrently. | ||
| /// | ||
| /// Returns a vec of results - one per URL. Failed fetches return errors | ||
| /// rather than failing the entire batch, so callers can decide how to handle | ||
| /// partial failures. | ||
| pub async fn fetch_signed_contexts(urls: &[String]) -> Vec<Result<SignedContextV1, OracleError>> { | ||
| let futures: Vec<_> = urls.iter().map(|url| fetch_signed_context(url)).collect(); | ||
|
|
||
| futures::future::join_all(futures).await | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find where fetch_signed_context and fetch_signed_contexts are called
rg "fetch_signed_context" --type rust -A 3 -B 3Repository: rainlanguage/rain.orderbook
Length of output: 4051
🏁 Script executed:
# Look for oracle_url usage and how it's obtained
rg "oracle_url" --type rust -A 3 -B 3Repository: rainlanguage/rain.orderbook
Length of output: 4737
🏁 Script executed:
# Check OracleError definition to understand current error handling
rg "OracleError" --type rust -A 5 | head -50Repository: rainlanguage/rain.orderbook
Length of output: 1240
🏁 Script executed:
# Search for order metadata or similar patterns related to oracle URLs
rg "order.*metadata|metadata.*oracle" --type rust -A 3 -B 3Repository: rainlanguage/rain.orderbook
Length of output: 6648
🏁 Script executed:
# Check the full oracle.rs file to understand context
cat -n crates/common/src/oracle.rsRepository: rainlanguage/rain.orderbook
Length of output: 4308
🏁 Script executed:
# Search for oracle endpoint documentation or trust assumptions
rg "oracle.*endpoint|oracle.*trusted|oracle.*url.*valid" --type rust -i -B 3 -A 3Repository: rainlanguage/rain.orderbook
Length of output: 2052
🏁 Script executed:
# Look for URL validation patterns elsewhere in the codebase
rg "Url::parse|url.*parse|https.*check|localhost.*block" --type rust -B 2 -A 2Repository: rainlanguage/rain.orderbook
Length of output: 50384
🏁 Script executed:
# Check if there's any configuration or allowlist for oracle URLs
rg "oracle.*config|allowlist|whitelist" --type rust -i -B 2 -A 2Repository: rainlanguage/rain.orderbook
Length of output: 53
🏁 Script executed:
# Look at the ParsedMeta to understand oracle URL origin better
rg "SignedContextOracleV1|ParsedMeta" --type rust -B 3 -A 3 | head -60Repository: rainlanguage/rain.orderbook
Length of output: 4527
Add URL validation to prevent SSRF attacks via oracle endpoints.
Oracle URLs originate from user-submitted order metadata (via ParsedMeta::SignedContextOracleV1) and are fetched without validation. This creates an SSRF vector—an attacker could supply URLs targeting internal services, localhost, or private IP ranges.
Enforce HTTPS, block localhost/loopback addresses, and consider blocking private IP ranges before issuing the HTTP request. The OracleError::InvalidUrl variant already exists to support this validation. If oracle URLs are guaranteed trusted, document that assumption in the function comments.
🛡️ Suggested guardrail
+use std::net::IpAddr;
+use url::Url;
+
pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> {
+ let parsed = Url::parse(url).map_err(|e| OracleError::InvalidUrl(e.to_string()))?;
+ if parsed.scheme() != "https" {
+ return Err(OracleError::InvalidUrl("oracle URL must use https".to_string()));
+ }
+ let host = parsed
+ .host_str()
+ .ok_or_else(|| OracleError::InvalidUrl("oracle URL missing host".to_string()))?;
+ if host.eq_ignore_ascii_case("localhost") || host.ends_with(".localhost") {
+ return Err(OracleError::InvalidUrl("oracle URL must not target localhost".to_string()));
+ }
+ if let Ok(ip) = host.parse::<IpAddr>() {
+ if ip.is_loopback() {
+ return Err(OracleError::InvalidUrl("oracle URL must not target loopback".to_string()));
+ }
+ }
+
let client = Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()?;📝 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.
| /// Fetch signed context from an oracle endpoint. | |
| /// | |
| /// The endpoint must respond to a GET request with a JSON body matching | |
| /// `OracleResponse` (signer, context, signature). | |
| pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> { | |
| let client = Client::builder() | |
| .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS)) | |
| .build()?; | |
| let response: OracleResponse = client | |
| .get(url) | |
| .send() | |
| .await? | |
| .error_for_status()? | |
| .json() | |
| .await?; | |
| Ok(response.into()) | |
| } | |
| /// Fetch signed contexts for multiple oracle URLs concurrently. | |
| /// | |
| /// Returns a vec of results - one per URL. Failed fetches return errors | |
| /// rather than failing the entire batch, so callers can decide how to handle | |
| /// partial failures. | |
| pub async fn fetch_signed_contexts(urls: &[String]) -> Vec<Result<SignedContextV1, OracleError>> { | |
| let futures: Vec<_> = urls.iter().map(|url| fetch_signed_context(url)).collect(); | |
| futures::future::join_all(futures).await | |
| } | |
| use std::net::IpAddr; | |
| use url::Url; | |
| /// Fetch signed context from an oracle endpoint. | |
| /// | |
| /// The endpoint must respond to a GET request with a JSON body matching | |
| /// `OracleResponse` (signer, context, signature). | |
| pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> { | |
| let parsed = Url::parse(url).map_err(|e| OracleError::InvalidUrl(e.to_string()))?; | |
| if parsed.scheme() != "https" { | |
| return Err(OracleError::InvalidUrl("oracle URL must use https".to_string())); | |
| } | |
| let host = parsed | |
| .host_str() | |
| .ok_or_else(|| OracleError::InvalidUrl("oracle URL missing host".to_string()))?; | |
| if host.eq_ignore_ascii_case("localhost") || host.ends_with(".localhost") { | |
| return Err(OracleError::InvalidUrl("oracle URL must not target localhost".to_string())); | |
| } | |
| if let Ok(ip) = host.parse::<IpAddr>() { | |
| if ip.is_loopback() { | |
| return Err(OracleError::InvalidUrl("oracle URL must not target loopback".to_string())); | |
| } | |
| } | |
| let client = Client::builder() | |
| .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS)) | |
| .build()?; | |
| let response: OracleResponse = client | |
| .get(url) | |
| .send() | |
| .await? | |
| .error_for_status()? | |
| .json() | |
| .await?; | |
| Ok(response.into()) | |
| } | |
| /// Fetch signed contexts for multiple oracle URLs concurrently. | |
| /// | |
| /// Returns a vec of results - one per URL. Failed fetches return errors | |
| /// rather than failing the entire batch, so callers can decide how to handle | |
| /// partial failures. | |
| pub async fn fetch_signed_contexts(urls: &[String]) -> Vec<Result<SignedContextV1, OracleError>> { | |
| let futures: Vec<_> = urls.iter().map(|url| fetch_signed_context(url)).collect(); | |
| futures::future::join_all(futures).await | |
| } |
🤖 Prompt for AI Agents
In `@crates/common/src/oracle.rs` around lines 44 - 73, Add URL validation in
fetch_signed_context (and ensure callers like fetch_signed_contexts benefit) by
parsing the input with Url::parse and rejecting non-HTTPS schemes (return
OracleError::InvalidUrl). After parsing, resolve the host to IPs (e.g., via
lookup or DNS resolution) and reject loopback/localhost and private IP ranges
(RFC1918/unique local IPv6) before constructing the reqwest Client and issuing
the request; return OracleError::InvalidUrl on validation failures. Keep the
existing behavior of returning other OracleError variants for network/json
failures, and if you intend to trust oracle URLs, instead document that
assumption in the function comment.
| #[test] | ||
| fn test_from_meta_item_signed_context_oracle_v1() { | ||
| let oracle = SignedContextOracleV1::parse("https://oracle.example.com/prices/eth-usd") | ||
| .unwrap(); | ||
| let item = oracle.to_meta_item(); | ||
| let result = ParsedMeta::from_meta_item(&item).unwrap(); | ||
| match result.unwrap() { | ||
| ParsedMeta::SignedContextOracleV1(parsed_oracle) => { | ||
| assert_eq!(parsed_oracle.url(), "https://oracle.example.com/prices/eth-usd"); | ||
| } | ||
| _ => panic!("Expected SignedContextOracleV1"), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_multiple_with_oracle() { | ||
| let source = get_default_dotrain_source(); | ||
| let oracle = SignedContextOracleV1::parse("https://oracle.example.com/feed").unwrap(); | ||
|
|
||
| let items = vec![ | ||
| RainMetaDocumentV1Item::from(source.clone()), | ||
| oracle.to_meta_item(), | ||
| ]; | ||
|
|
||
| let results = ParsedMeta::parse_multiple(&items).unwrap(); | ||
| assert_eq!(results.len(), 2); | ||
|
|
||
| match &results[0] { | ||
| ParsedMeta::DotrainSourceV1(parsed_source) => { | ||
| assert_eq!(parsed_source.0, source.0); | ||
| } | ||
| _ => panic!("Expected DotrainSourceV1"), | ||
| } | ||
|
|
||
| match &results[1] { | ||
| ParsedMeta::SignedContextOracleV1(parsed_oracle) => { | ||
| assert_eq!(parsed_oracle.url(), "https://oracle.example.com/feed"); | ||
| } | ||
| _ => panic!("Expected SignedContextOracleV1"), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_from_bytes_with_oracle() { | ||
| let oracle = | ||
| SignedContextOracleV1::parse("https://oracle.example.com/prices/eth-usd").unwrap(); | ||
| let items = vec![oracle.to_meta_item()]; | ||
| let bytes = RainMetaDocumentV1Item::cbor_encode_seq(&items, KnownMagic::RainMetaDocumentV1) | ||
| .unwrap(); | ||
|
|
||
| let parsed = ParsedMeta::parse_from_bytes(&bytes).unwrap(); | ||
| assert_eq!(parsed.len(), 1); | ||
| assert!( | ||
| matches!(&parsed[0], ParsedMeta::SignedContextOracleV1(o) if o.url() == "https://oracle.example.com/prices/eth-usd") | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider insta snapshots for the new oracle parsing tests.
Assertions are fine, but snapshots can make future metadata changes easier to review. As per coding guidelines, "Prefer insta snapshots for Rust testing".
🤖 Prompt for AI Agents
In `@crates/common/src/parsed_meta.rs` around lines 210 - 265, Tests for oracle
parsing (test_from_meta_item_signed_context_oracle_v1,
test_parse_multiple_with_oracle, test_parse_from_bytes_with_oracle) should use
insta snapshots instead of/manual assertions; update each test to produce a
serializable representation (e.g., the ParsedMeta result or the
SignedContextOracleV1 via SignedContextOracleV1::to_meta_item /
ParsedMeta::from_meta_item / ParsedMeta::parse_multiple /
ParsedMeta::parse_from_bytes) and call insta::assert_yaml_snapshot! (or
assert_debug_snapshot!) to record the output, replacing or augmenting the
current assert_eq! / matches! checks so future metadata changes are captured by
snapshots. Ensure the tests import insta and use stable snapshot names tied to
the test function names.
| type QuoteWithContext = ( | ||
| Result<Vec<RaindexOrderQuote>, RaindexError>, | ||
| Vec<SignedContextV1>, | ||
| ); | ||
|
|
||
| // Fetch quotes and oracle data concurrently for each order | ||
| let results: Vec<QuoteWithContext> = futures::stream::iter(orders.iter().map(|order| { | ||
| let gas_string = gas_string.clone(); | ||
| async move { | ||
| let quotes = order.get_quotes(block_number, gas_string).await; | ||
| let signed_context = fetch_oracle_for_order(order).await; | ||
| (quotes, signed_context) | ||
| } | ||
| })) | ||
| .buffered(DEFAULT_QUOTE_CONCURRENCY) | ||
| .collect() | ||
| .await; | ||
|
|
||
| orders | ||
| .iter() | ||
| .zip(quote_results) | ||
| .map(|(order, quotes_result)| { | ||
| build_candidates_for_order(order, quotes_result?, input_token, output_token) | ||
| .zip(results) | ||
| .map(|(order, (quotes_result, signed_context))| { | ||
| build_candidates_for_order( | ||
| order, | ||
| quotes_result?, | ||
| input_token, | ||
| output_token, | ||
| signed_context, | ||
| ) |
There was a problem hiding this comment.
Comment says “concurrent” but per‑order fetch is sequential.
Inside each task, quotes are awaited before oracle fetch. Either update the comment or run both futures concurrently.
✏️ Minimal fix (comment only)
- // Fetch quotes and oracle data concurrently for each order
+ // Fetch quotes and oracle data per order (parallelized across orders)🤖 Prompt for AI Agents
In `@crates/common/src/take_orders/candidates.rs` around lines 94 - 122, The
comment claims per-order work is concurrent but the code awaits order.get_quotes
before calling fetch_oracle_for_order, making them sequential; fix by starting
both futures before awaiting (e.g., create quotes_fut =
order.get_quotes(block_number, gas_string) and signed_context_fut =
fetch_oracle_for_order(order) then await both together with futures::join! or
join to produce (quotes, signed_context)), referencing the futures creation in
the map/async block where order.get_quotes and fetch_oracle_for_order are
called; alternatively, if you prefer the minimal change, update the comment to
remove "concurrently" so it accurately reflects the current sequential behavior.
crates/quote/src/order_quotes.rs
Outdated
| /// Get order quotes with optional signed context data. | ||
| /// The signed_context is applied to all quote targets for all orders. | ||
| pub async fn get_order_quotes_with_context( | ||
| orders: Vec<SgOrder>, | ||
| block_number: Option<u64>, | ||
| rpcs: Vec<String>, | ||
| gas: Option<u64>, | ||
| signed_context: Vec<SignedContextV1>, | ||
| ) -> Result<Vec<BatchOrderQuotesResponse>, Error> { |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the function definition and all call sites
rg -n "get_order_quotes_with_context" crates/quote/src/Repository: rainlanguage/rain.orderbook
Length of output: 267
🏁 Script executed:
#!/bin/bash
# Look at the function implementation and surrounding context
cat -n crates/quote/src/order_quotes.rs | head -100Repository: rainlanguage/rain.orderbook
Length of output: 4518
🏁 Script executed:
#!/bin/bash
# Search for external callers across the codebase
rg -n "get_order_quotes_with_context" --type rs -C 2Repository: rainlanguage/rain.orderbook
Length of output: 97
🏁 Script executed:
#!/bin/bash
# Get the complete order_quotes.rs file to see how signed_context is used
wc -l crates/quote/src/order_quotes.rsRepository: rainlanguage/rain.orderbook
Length of output: 107
🏁 Script executed:
#!/bin/bash
# Search for external call sites across the codebase
rg "get_order_quotes_with_context" -C 3 --max-count 20Repository: rainlanguage/rain.orderbook
Length of output: 2099
🏁 Script executed:
#!/bin/bash
# Read the complete call site in crates/common/src/raindex_client/order_quotes.rs around the function call
cat -n crates/common/src/raindex_client/order_quotes.rs | sed -n '1,200p'Repository: rainlanguage/rain.orderbook
Length of output: 9717
🏁 Script executed:
#!/bin/bash
# Search more broadly for any multi-order batching patterns
rg "get_order_quotes" -C 5 | head -100Repository: rainlanguage/rain.orderbook
Length of output: 7001
🏁 Script executed:
#!/bin/bash
# Read the implementation of get_order_quotes_with_context to see how signed_context is used
cat -n crates/quote/src/order_quotes.rs | sed -n '50,250p'Repository: rainlanguage/rain.orderbook
Length of output: 8573
🏁 Script executed:
#!/bin/bash
# Search for uses of signed_context variable within the file
rg "signed_context" crates/quote/src/order_quotes.rs -n -C 2Repository: rainlanguage/rain.orderbook
Length of output: 761
🏁 Script executed:
#!/bin/bash
# Check if there's any documentation or comments about the intended use case
rg -n "signed_context\|SignedContextV1" crates/quote/src/order_quotes.rs -B 3 -A 3 | head -60Repository: rainlanguage/rain.orderbook
Length of output: 53
🏁 Script executed:
#!/bin/bash
# Check if there are any tests that use signed_context
rg -n "signed_context" crates/quote/src/order_quotes.rs | tail -20Repository: rainlanguage/rain.orderbook
Length of output: 256
Confirmation: Current batched callers only batch single orders and do not require per-order signed context.
All existing callers (e.g., crates/common/src/raindex_client/order_quotes.rs:138–144) batch single orders and fetch the signed context per-order from the oracle, then pass it to the function. The API design—which allows multiple orders but applies a single context to all—is not currently violated in practice. However, the function signature is misleading for future maintainers: the parameter signed_context: Vec<SignedContextV1> should either be documented more explicitly or the API redesigned to prevent accidental misuse if multi-order batching with different oracle contexts becomes required.
🤖 Prompt for AI Agents
In `@crates/quote/src/order_quotes.rs` around lines 50 - 58, The function
get_order_quotes_with_context accepts a Vec<SignedContextV1> but in practice
callers only pass a single per-order context and the API is misleading; change
the signature to accept an Option<SignedContextV1> (or a single SignedContextV1)
to make it explicit that one signed context is applied to all orders, update the
doc comment to state "signed_context is a single signed context applied to all
quote targets for all orders", and adjust any call sites (e.g., where callers
fetch per-order context) to pass the single context or None accordingly; keep
SgOrder and SignedContextV1 types unchanged.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@crates/common/src/oracle.rs`:
- Around line 15-16: OracleError::InvalidUrl is dead code; either remove the
variant from the OracleError enum or add URL validation where external URLs are
accepted (e.g., the Oracle constructor/factory or any function that accepts
endpoint strings) and return OracleError::InvalidUrl with the offending String
when validation fails; update callers to handle the new error if you implement
validation.
crates/common/src/oracle.rs
Outdated
| #[error("Invalid URL: {0}")] | ||
| InvalidUrl(String), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg "OracleError::InvalidUrl|InvalidUrl\(" --type rust -nRepository: rainlanguage/rain.orderbook
Length of output: 125
Remove unused InvalidUrl variant or implement URL validation.
OracleError::InvalidUrl is defined but never constructed anywhere in the codebase. Either remove this dead-code variant or implement URL validation logic that produces it.
🤖 Prompt for AI Agents
In `@crates/common/src/oracle.rs` around lines 15 - 16, OracleError::InvalidUrl is
dead code; either remove the variant from the OracleError enum or add URL
validation where external URLs are accepted (e.g., the Oracle
constructor/factory or any function that accepts endpoint strings) and return
OracleError::InvalidUrl with the offending String when validation fails; update
callers to handle the new error if you implement validation.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte (1)
11-26: 🧹 Nitpick | 🔵 TrivialMerge
Badgeinto the existingflowbite-svelteimport.
Badge(line 26) is imported separately fromflowbite-svelte, but there's already an import block from the same package on lines 11-17. Consolidating avoids a duplicate import source.♻️ Suggested merge
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, - TableHeadCell + TableHeadCell, + Badge } from 'flowbite-svelte'; ... -import { Badge } from 'flowbite-svelte';
🤖 Fix all issues with AI agents
In `@packages/ui-components/src/lib/components/detail/OrderDetail.svelte`:
- Around line 203-225: The anchor rendering for data.oracleUrl in
OrderDetail.svelte can be abused with javascript: URIs; validate and sanitize
the URL before using it in the href of the <a> inside CardProperty: ensure the
value parsed from data.oracleUrl has an allowed scheme (http or https) using a
URL parser, and if it is missing/invalid or not http(s) render it as plain text
(or a disabled/non-clickable element) instead of setting href, preserving
current target/rel attributes for valid URLs only; update the logic around
data.oracleUrl to perform this check and fallback display so malicious
javascript: URIs cannot be injected.
packages/ui-components/src/lib/components/detail/OrderDetail.svelte
Outdated
Show resolved
Hide resolved
929dcff to
1d940d3
Compare
… flows Phase 3 of signed context oracle discovery: - New oracle.rs module with fetch_signed_context(url) and OracleResponse type - OracleResponse maps directly to SignedContextV1 (signer, context as bytes32[], signature) - Added signed_context field to TakeOrderCandidate - Wired oracle fetching into: - build_take_order_candidates_for_pair (batch flow, concurrent fetch) - execute_single_take (single take flow, oracle_url param) - build_take_orders_config_from_simulation (passes through to TakeOrderConfigV4) - Oracle fetch is best-effort: failures log a warning and use empty signed context - 3 oracle tests + 9 parsed_meta tests passing
- Add get_order_quotes_with_context() to quote crate (accepts signed_context param) - RaindexOrder.get_quotes() now fetches oracle data and passes to quotes - Original get_order_quotes() unchanged (delegates with empty context)
reqwest::ClientBuilder::timeout() is not available on WASM targets. Use cfg(not(target_family = "wasm")) to only set it on native.
- OrderDetail: show Oracle card property with URL link when order has oracle metadata - Includes tooltip explaining signed context oracle usage - TanstackOrderQuote: show purple 'Oracle' badge next to quotes heading when oracle is active - Indicates quotes include signed context data from oracle - Both use the oracleUrl getter exposed via WASM bindings on RaindexOrder
The oracle endpoint now receives order details via POST so it can tailor responses based on the specific order, counterparty, and IO indexes. POST body: abi.encode(OrderV4, inputIOIndex, outputIOIndex, counterparty) Falls back to GET when no body is provided (simple oracles). Callers currently pass None - ABI encoding will be wired in once the order data is available at each call site.
POST with ABI-encoded order data is mandatory. Callers currently pass empty vec — will be wired to abi.encode(OrderV4, inputIOIndex, outputIOIndex, counterparty) at each call site.
- encode_oracle_body: abi.encode(OrderV4, inputIOIndex, outputIOIndex, counterparty) - get_quotes: fetches oracle per IO pair concurrently, counterparty=address(0) - build_take_order_candidates: fetches oracle per quote pair - execute_single_take: encodes with actual taker as counterparty - get_order_quotes_with_context_fn: accepts per-pair context callback
- Oracle fetch logic moved from common to quote crate (common re-exports) - get_order_quotes now extracts oracle URL directly from SgOrder.meta - Removed get_order_quotes_with_context and get_order_quotes_with_context_fn - No more closures, HashMaps, or pre-fetching — oracle context fetched inline per IO pair inside the quote loop - RaindexOrder.get_quotes() simplified to just call get_order_quotes()
1d940d3 to
63ea51f
Compare
- Add encode_oracle_body_batch() for array encoding: abi.encode((OrderV4, uint256, uint256, address)[]) - Update fetch_signed_context_batch() to handle Vec responses - Maintain backward compatibility with single request functions - Add comprehensive tests for both single and batch formats - Response format now expects JSON array per spec
Caution
Merge order: Merge #2457 first, then this PR.
Motivation
Orders with
SignedContextOracleV1metadata now expose anoracle_url()(via #2457), but the SDK still hardcodessignedContexttovec![]in all take-order flows. Takers need the SDK to automatically fetch and pass signed context.Solution
oracle.rsmodule:fetch_signed_context(url)— HTTP GET with timeout, returnsSignedContextV1TakeOrderCandidatenow carriessigned_context: Vec<SignedContextV1>build_take_order_candidates_for_pair): fetches oracle data concurrently alongside quotesexecute_single_take): acceptsoracle_url: Option<String>, fetches before simulationbuild_take_orders_config_from_simulation): passessigned_contextthrough toTakeOrderConfigV4Checks
By submitting this for review, I'm confirming I've done the following: