Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 43 additions & 42 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,68 +19,69 @@ jobs:
test_dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: |
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
Comment on lines +22 to +23
Copy link

@coderabbitai coderabbitai bot Sep 30, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Upgrade outdated GitHub Actions.

The static analysis tool correctly identifies that these actions are outdated and may fail on GitHub Actions:

  • actions/checkout@v3 uses Node 16, which is deprecated. Upgrade to actions/checkout@v4.
  • actions-rs/toolchain@v1 is from an archived repository and is no longer maintained. Consider using dtolnay/rust-toolchain or the official actions/setup-rust instead.

Apply this diff to upgrade the actions across all jobs:

-      - uses: actions/checkout@v3
-      - uses: actions-rs/toolchain@v1
+      - uses: actions/checkout@v4
+      - uses: dtolnay/rust-toolchain@stable
-        with:
-          toolchain: stable
-          override: true

Note: dtolnay/rust-toolchain uses the version as a tag (e.g., @stable), so the with block is no longer needed.

Also applies to: 45-46, 68-69

🧰 Tools
🪛 actionlint (1.7.7)

22-22: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


23-23: the runner of "actions-rs/toolchain@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
.github/workflows/ci.yaml lines 22-23 (and similarly update lines 45-46 and
68-69): replace the outdated actions by upgrading actions/checkout@v3 to
actions/checkout@v4 and remove the archived actions-rs/toolchain@v1; instead use
a maintained Rust action such as actions/setup-rust@v1 (or
dtolnay/rust-toolchain@stable if you prefer tag syntax) and adjust any with:
block accordingly (dtolnay variant does not need a with block,
actions/setup-rust keeps the toolchain input).

Copy link
Contributor

Choose a reason for hiding this comment

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

Please fix this as a separate PR

Copy link

Choose a reason for hiding this comment

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

@JasoonS, understood! I'll defer this change to a separate PR to keep this one focused on the negative queries work.

Would you like me to create a GitHub issue to track the GitHub Actions upgrade (actions/checkout@v3 → v4 and replacing actions-rs/toolchain@v1 with a maintained alternative like dtolnay/rust-toolchain)?

with:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y capnproto libcapnp-dev
- name: Check Rust version
run: |
- name: Check Rust version
run: |
rustc --version
cargo --version
rustup show
- name: Build
run: cargo build
- name: Test
run: cargo test
- name: Build
run: cargo build
- name: Test
run: cargo test

test_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: |
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y capnproto libcapnp-dev
- name: Check Rust version
run: |
- name: Check Rust version
run: |
rustc --version
cargo --version
rustup show
- name: Build
run: cargo build --release
- name: Test
run: cargo test --release
- name: Build
run: cargo build --release
- name: Test
run: cargo test --release

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: |
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y capnproto libcapnp-dev
- name: Check Rust version
run: |
- name: Check Rust version
run: |
rustc --version
cargo --version
rustup show
- name: Rustfmt
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -Dwarnings
- name: Rustfmt
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -Dwarnings
4 changes: 2 additions & 2 deletions hypersync-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hypersync-client"
version = "0.19.0"
version = "0.20.0"
edition = "2021"
description = "client library for hypersync"
license = "MPL-2.0"
Expand Down Expand Up @@ -47,7 +47,7 @@ nohash-hasher = "0.2.0"
ethers = { version = "2.0.14", optional = true }
alloy-primitives = "1.1"

hypersync-net-types = { path = "../hypersync-net-types", version = "0.10" }
hypersync-net-types = { path = "../hypersync-net-types", version = "0.11" }
hypersync-format = { path = "../hypersync-format", version = "0.5" }
hypersync-schema = { path = "../hypersync-schema", version = "0.3" }

Expand Down
16 changes: 9 additions & 7 deletions hypersync-client/src/preset_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::collections::BTreeSet;

use arrayvec::ArrayVec;
use hypersync_format::{Address, LogArgument};
use hypersync_net_types::{FieldSelection, LogSelection, Query, TransactionSelection};
use hypersync_net_types::{
FieldSelection, LogFilter, LogSelection, Query, TransactionFilter, TransactionSelection,
};

/// Returns a query for all Blocks and Transactions within the block range (from_block, to_block]
/// If to_block is None then query runs to the head of the chain.
Expand Down Expand Up @@ -81,10 +83,10 @@ pub fn logs(from_block: u64, to_block: Option<u64>, contract_address: Address) -
Query {
from_block,
to_block,
logs: vec![LogSelection {
logs: vec![LogSelection::from(LogFilter {
address: vec![contract_address],
..Default::default()
}],
})],
field_selection: FieldSelection {
log: all_log_fields,
..Default::default()
Expand Down Expand Up @@ -116,11 +118,11 @@ pub fn logs_of_event(
Query {
from_block,
to_block,
logs: vec![LogSelection {
logs: vec![LogSelection::from(LogFilter {
address: vec![contract_address],
topics,
..Default::default()
}],
})],
field_selection: FieldSelection {
log: all_log_fields,
..Default::default()
Expand Down Expand Up @@ -170,10 +172,10 @@ pub fn transactions_from_address(
Query {
from_block,
to_block,
transactions: vec![TransactionSelection {
transactions: vec![TransactionSelection::from(TransactionFilter {
from: vec![address],
..Default::default()
}],
})],
field_selection: FieldSelection {
transaction: all_txn_fields,
..Default::default()
Expand Down
6 changes: 3 additions & 3 deletions hypersync-client/tests/api_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use hypersync_client::{
preset_query, simple_types::Transaction, Client, ClientConfig, ColumnMapping, StreamConfig,
};
use hypersync_format::{Address, FilterWrapper, Hex, LogArgument};
use hypersync_net_types::{FieldSelection, Query, TransactionSelection};
use hypersync_net_types::{FieldSelection, Query, TransactionFilter, TransactionSelection};
use polars_arrow::array::UInt64Array;

#[tokio::test(flavor = "multi_thread")]
Expand Down Expand Up @@ -458,10 +458,10 @@ async fn test_small_bloom_filter_query() {
from_block: 19_000_000,
to_block: Some(19_300_000),
logs: Vec::new(),
transactions: vec![TransactionSelection {
transactions: vec![TransactionSelection::from(TransactionFilter {
from_filter: Some(from_address_filter),
..Default::default()
}],
})],
field_selection: FieldSelection {
block: Default::default(),
log: Default::default(),
Expand Down
2 changes: 1 addition & 1 deletion hypersync-net-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hypersync-net-types"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
description = "hypersync types for transport over network"
license = "MPL-2.0"
Expand Down
42 changes: 38 additions & 4 deletions hypersync-net-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,30 @@ pub mod hypersync_net_types_capnp {
}

#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct BlockSelection {
pub struct Selection<T> {
/// Filters where matching values should be included in the response
/// Default::default() means include everything
#[serde(default, flatten)]
pub include: T,
/// Filters where matching values should be excluded from the response
/// None means exclude nothing, Some(Default::default()) means exclude everything
#[serde(default)]
pub exclude: Option<T>,
}
Comment on lines 14 to +24
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Document the exclude field behavior and validate the design.

The new Selection<T> generic introduces an include/exclude pattern that enables negative queries (matching the PR title). However, the exclude field lacks documentation on:

  1. How exclude interacts with include (e.g., is it applied after include filtering?)
  2. What Some(Default::default()) means in practice (exclude everything that passes include?)
  3. Server-side implementation requirements for this pattern

Additionally, the #[serde(flatten)] on include will merge its fields into the parent during serialization, which may cause naming conflicts if T has an exclude field.

Consider adding comprehensive documentation:

 #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
 pub struct Selection<T> {
     /// Filters where matching values should be included in the response
-    /// Default::default() means include everything
+    /// Default::default() means include everything.
+    /// Items must match the include filter to be considered for the response.
     #[serde(default, flatten)]
     pub include: T,
     /// Filters where matching values should be excluded from the response
-    /// None means exclude nothing, Some(Default::default()) means exclude everything
+    /// None means exclude nothing. Some(Default::default()) means exclude everything.
+    /// Exclusion is applied after inclusion, so an item matching both include and exclude
+    /// filters will be excluded from the response.
     #[serde(default)]
     pub exclude: Option<T>,
 }
📝 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
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct BlockSelection {
pub struct Selection<T> {
/// Filters where matching values should be included in the response
/// Default::default() means include everything
#[serde(default, flatten)]
pub include: T,
/// Filters where matching values should be excluded from the response
/// None means exclude nothing, Some(Default::default()) means exclude everything
#[serde(default)]
pub exclude: Option<T>,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Selection<T> {
/// Filters where matching values should be included in the response
/// Default::default() means include everything.
/// Items must match the include filter to be considered for the response.
#[serde(default, flatten)]
pub include: T,
/// Filters where matching values should be excluded from the response
/// None means exclude nothing. Some(Default::default()) means exclude everything.
/// Exclusion is applied after inclusion, so an item matching both include and exclude
/// filters will be excluded from the response.
#[serde(default)]
pub exclude: Option<T>,
}


impl<T> From<T> for Selection<T> {
fn from(include: T) -> Self {
Self {
include,
exclude: None,
}
}
}

pub type BlockSelection = Selection<BlockFilter>;

#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct BlockFilter {
/// Hash of a block, any blocks that have one of these hashes will be returned.
/// Empty means match all.
#[serde(default)]
Expand All @@ -23,8 +46,10 @@ pub struct BlockSelection {
pub miner: Vec<Address>,
}

pub type LogSelection = Selection<LogFilter>;

#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct LogSelection {
pub struct LogFilter {
/// Address of the contract, any logs that has any of these addresses will be returned.
/// Empty means match all.
#[serde(default)]
Expand All @@ -37,8 +62,10 @@ pub struct LogSelection {
pub topics: ArrayVec<Vec<LogArgument>, 4>,
}

pub type TransactionSelection = Selection<TransactionFilter>;

#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct TransactionSelection {
pub struct TransactionFilter {
/// Address the transaction should originate from. If transaction.from matches any of these, the transaction
/// will be returned. Keep in mind that this has an and relationship with to filter, so each transaction should
/// match both of them. Empty means match all.
Expand Down Expand Up @@ -90,8 +117,10 @@ pub struct AuthorizationSelection {
pub address: Vec<Address>,
}

pub type TraceSelection = Selection<TraceFilter>;

#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct TraceSelection {
pub struct TraceFilter {
#[serde(default)]
pub from: Vec<Address>,
#[serde(default)]
Expand Down Expand Up @@ -177,8 +206,13 @@ pub struct Query {

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
pub enum JoinMode {
/// Join in this order logs -> transactions -> traces -> blocks
Default,
/// Join everything to everything. For example if logSelection matches log0, we get the
/// associated transaction of log0 and then we get associated logs of that transaction as well. Applites similarly
/// to blocks, traces.
JoinAll,
/// JoinNothing: join nothing.
JoinNothing,
}

Expand Down
Loading