Skip to content

Commit

Permalink
feat: remove nullifiers from SyncState endpoint (#708)
Browse files Browse the repository at this point in the history
* feat: add block_num parameter to check_nullifiers_by_prefix

* chore: update CHANGELOG

* review: add `block_num` parameter as required

* review: update CHANGELOG

* review: update doc comment

* feat: remove nullifiers from `SyncState` endpoint

* chore: update doc comments

* chore: update CHANGELOG

* chore: update rpc README
  • Loading branch information
TomasArrachea committed Feb 20, 2025
1 parent 73884ff commit f0b01a4
Show file tree
Hide file tree
Showing 16 changed files with 179 additions and 569 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [BREAKING] Updated minimum Rust version to 1.84.
- [BREAKING] `Endpoint` configuration simplified to a single string (#654).
- [BREAKING] `CheckNullifiersByPrefix` now takes a starting block number (#707).
- [BREAKING] Removed nullifiers from `SyncState` endpoint (#708).

### Enhancements

Expand Down
6 changes: 1 addition & 5 deletions crates/proto/src/generated/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub struct GetBlockHeaderByNumberRequest {
///
/// Specifies state updates the client is interested in. The server will return the first block which
/// contains a note matching `note_tags` or the chain tip. And the corresponding updates to
/// `nullifiers` and `account_ids` for that block range.
/// `account_ids` for that block range.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SyncStateRequest {
/// Last block known by the client. The response will contain data starting from the next block,
Expand All @@ -63,10 +63,6 @@ pub struct SyncStateRequest {
/// Specifies the tags which the client is interested in.
#[prost(fixed32, repeated, tag = "3")]
pub note_tags: ::prost::alloc::vec::Vec<u32>,
/// Determines the nullifiers the client is interested in by specifying the 16high bits of the
/// target nullifier.
#[prost(uint32, repeated, tag = "4")]
pub nullifiers: ::prost::alloc::vec::Vec<u32>,
}
/// Note synchronization request.
///
Expand Down
7 changes: 1 addition & 6 deletions crates/proto/src/generated/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ pub struct SyncStateResponse {
/// List of all notes together with the Merkle paths from `response.block_header.note_root`.
#[prost(message, repeated, tag = "7")]
pub notes: ::prost::alloc::vec::Vec<super::note::NoteSyncRecord>,
/// List of nullifiers created between `request.block_num + 1` and `response.block_header.block_num`.
#[prost(message, repeated, tag = "8")]
pub nullifiers: ::prost::alloc::vec::Vec<NullifierUpdate>,
}
/// Represents the result of syncing notes request.
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down Expand Up @@ -124,9 +121,7 @@ pub struct GetBlockInputsResponse {
pub nullifiers: ::prost::alloc::vec::Vec<NullifierBlockInputRecord>,
/// The list of requested notes which were found in the database.
#[prost(message, optional, tag = "5")]
pub found_unauthenticated_notes: ::core::option::Option<
super::note::NoteAuthenticationInfo,
>,
pub found_unauthenticated_notes: ::core::option::Option<super::note::NoteAuthenticationInfo>,
}
/// Represents the result of getting batch inputs.
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down
473 changes: 158 additions & 315 deletions crates/proto/src/generated/rpc.rs

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions crates/rpc-proto/proto/requests.proto
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ message GetBlockHeaderByNumberRequest {
//
// Specifies state updates the client is interested in. The server will return the first block which
// contains a note matching `note_tags` or the chain tip. And the corresponding updates to
// `nullifiers` and `account_ids` for that block range.
// `account_ids` for that block range.
message SyncStateRequest {
// Last block known by the client. The response will contain data starting from the next block,
// until the first block which contains a note of matching the requested tag, or the chain tip
Expand All @@ -59,10 +59,6 @@ message SyncStateRequest {

// Specifies the tags which the client is interested in.
repeated fixed32 note_tags = 3;

// Determines the nullifiers the client is interested in by specifying the 16high bits of the
// target nullifier.
repeated uint32 nullifiers = 4;
}

// Note synchronization request.
Expand Down
3 changes: 0 additions & 3 deletions crates/rpc-proto/proto/responses.proto
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ message SyncStateResponse {

// List of all notes together with the Merkle paths from `response.block_header.note_root`.
repeated note.NoteSyncRecord notes = 7;

// List of nullifiers created between `request.block_num + 1` and `response.block_header.block_num`.
repeated NullifierUpdate nullifiers = 8;
}

// Represents the result of syncing notes request.
Expand Down
8 changes: 4 additions & 4 deletions crates/rpc-proto/proto/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,19 @@ service Api {
rpc SyncNotes(requests.SyncNoteRequest) returns (responses.SyncNoteResponse) {}

// Returns info which can be used by the client to sync up to the latest state of the chain
// for the objects (accounts, notes, nullifiers) the client is interested in.
// for the objects (accounts and notes) the client is interested in.
//
// This request returns the next block containing requested data. It also returns `chain_tip`
// which is the latest block number in the chain. Client is expected to repeat these requests
// in a loop until `response.block_header.block_num == response.chain_tip`, at which point
// the client is fully synchronized with the chain.
//
// Each request also returns info about new notes, nullifiers etc. created. It also returns
// Each update response also contains info about new notes, accounts etc. created. It also returns
// Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain
// MMR peaks and chain MMR nodes.
//
// For preserving some degree of privacy, note tags and nullifiers filters contain only high
// part of hashes. Thus, returned data contains excessive notes and nullifiers, client can make
// For preserving some degree of privacy, note tags contain only high
// part of hashes. Thus, returned data contains excessive notes, client can make
// additional filtering of that data on its side.
rpc SyncState(requests.SyncStateRequest) returns (responses.SyncStateResponse) {}
}
11 changes: 5 additions & 6 deletions crates/rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,18 @@ the chain.

### SyncState

Returns info which can be used by the client to sync up to the latest state of the chain for the objects (accounts,
notes, nullifiers) the client is interested in.
Returns info which can be used by the client to sync up to the latest state of the chain for the objects (accounts and
notes) the client is interested in.

This request returns the next block containing requested data. It also returns `chain_tip` which is the latest block
number in the chain. Client is expected to repeat these requests in a loop until
`response.block_header.block_num == response.chain_tip`, at which point the client is fully synchronized with the chain.

Each request also returns info about new notes, nullifiers etc. created. It also returns Chain MMR delta that can be
Each request also returns info about new notes, accounts, etc. created. It also returns Chain MMR delta that can be
used to update the state of Chain MMR. This includes both chain MMR peaks and chain MMR nodes.

For preserving some degree of privacy, note tags and nullifiers filters contain only high part of hashes. Thus, returned
data contains excessive notes and nullifiers, client can make additional filtering of that data on its side.

For preserving some degree of privacy, note tags contain only high part of hashes. Thus, returned data contains excessive
notes, client can make additional filtering of that data on its side.
---

## License
Expand Down
6 changes: 1 addition & 5 deletions crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ pub struct StateSyncUpdate {
pub block_header: BlockHeader,
pub account_updates: Vec<AccountSummary>,
pub transactions: Vec<TransactionSummary>,
pub nullifiers: Vec<NullifierInfo>,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -328,15 +327,12 @@ impl Db {
block_num: BlockNumber,
account_ids: Vec<AccountId>,
note_tags: Vec<u32>,
nullifier_prefixes: Vec<u32>,
) -> Result<StateSyncUpdate, StateSyncError> {
self.pool
.get()
.await
.map_err(DatabaseError::MissingDbConnection)?
.interact(move |conn| {
sql::get_state_sync(conn, block_num, &account_ids, &note_tags, &nullifier_prefixes)
})
.interact(move |conn| sql::get_state_sync(conn, block_num, &account_ids, &note_tags))
.await
.map_err(|err| {
DatabaseError::InteractError(format!("Get state sync task failed: {err}"))
Expand Down
57 changes: 0 additions & 57 deletions crates/store/src/db/sql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,54 +617,6 @@ pub fn select_all_nullifiers(conn: &mut Connection) -> Result<Vec<(Nullifier, Bl
Ok(result)
}

/// Select nullifiers created between `(block_start, block_end]` that also match the
/// `nullifier_prefixes` filter using the given [Connection].
///
/// Each value of the `nullifier_prefixes` is only the 16 most significant bits of the nullifier of
/// interest to the client. This hides the details of the specific nullifier being requested.
///
/// # Returns
///
/// A vector of [`NullifierInfo`] with the nullifiers and the block height at which they were
/// created, or an error.
pub fn select_nullifiers_by_block_range(
conn: &mut Connection,
block_start: BlockNumber,
block_end: BlockNumber,
nullifier_prefixes: &[u32],
) -> Result<Vec<NullifierInfo>> {
let nullifier_prefixes: Vec<Value> =
nullifier_prefixes.iter().copied().map(Into::into).collect();

let mut stmt = conn.prepare_cached(
"
SELECT
nullifier,
block_num
FROM
nullifiers
WHERE
block_num > ?1 AND
block_num <= ?2 AND
nullifier_prefix IN rarray(?3)
ORDER BY
block_num ASC
",
)?;

let mut rows =
stmt.query(params![block_start.as_u32(), block_end.as_u32(), Rc::new(nullifier_prefixes)])?;

let mut result = Vec::new();
while let Some(row) = rows.next()? {
let nullifier_data = row.get_ref(0)?.as_blob()?;
let nullifier = Nullifier::read_from_bytes(nullifier_data)?;
let block_num: u32 = row.get(1)?;
result.push(NullifierInfo { nullifier, block_num: block_num.into() });
}
Ok(result)
}

/// Returns nullifiers filtered by prefix and block creation height.
///
/// Each value of the `nullifier_prefixes` is only the `prefix_len` most significant bits
Expand Down Expand Up @@ -1195,7 +1147,6 @@ pub fn get_state_sync(
block_num: BlockNumber,
account_ids: &[AccountId],
note_tag_prefixes: &[u32],
nullifier_prefixes: &[u32],
) -> Result<StateSyncUpdate, StateSyncError> {
let notes = select_notes_since_block_by_tag_and_sender(
conn,
Expand All @@ -1218,19 +1169,11 @@ pub fn get_state_sync(
account_ids,
)?;

let nullifiers = select_nullifiers_by_block_range(
conn,
block_num,
block_header.block_num(),
nullifier_prefixes,
)?;

Ok(StateSyncUpdate {
notes,
block_header,
account_updates,
transactions,
nullifiers,
})
}

Expand Down
128 changes: 0 additions & 128 deletions crates/store/src/db/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,134 +486,6 @@ fn sql_public_account_details() {
assert_eq!(read_delta, Some(delta2));
}

#[test]
fn sql_select_nullifiers_by_block_range() {
let mut conn = create_db();

// test empty table
let nullifiers =
sql::select_nullifiers_by_block_range(&mut conn, 0.into(), u32::MAX.into(), &[]).unwrap();
assert!(nullifiers.is_empty());

// test single item
let nullifier1 = num_to_nullifier(1 << 48);
let block_number1 = 1.into();
create_block(&mut conn, block_number1);

let transaction = conn.transaction().unwrap();
sql::insert_nullifiers_for_block(&transaction, &[nullifier1], block_number1).unwrap();
transaction.commit().unwrap();

let nullifiers = sql::select_nullifiers_by_block_range(
&mut conn,
0.into(),
u32::MAX.into(),
&[sql::utils::get_nullifier_prefix(&nullifier1)],
)
.unwrap();
assert_eq!(
nullifiers,
vec![NullifierInfo {
nullifier: nullifier1,
block_num: block_number1
}]
);

// test two elements
let nullifier2 = num_to_nullifier(2 << 48);
let block_number2 = 2.into();
create_block(&mut conn, block_number2);

let transaction = conn.transaction().unwrap();
sql::insert_nullifiers_for_block(&transaction, &[nullifier2], block_number2).unwrap();
transaction.commit().unwrap();

let nullifiers = sql::select_all_nullifiers(&mut conn).unwrap();
assert_eq!(nullifiers, vec![(nullifier1, block_number1), (nullifier2, block_number2)]);

// only the nullifiers matching the prefix are included
let nullifiers = sql::select_nullifiers_by_block_range(
&mut conn,
0.into(),
u32::MAX.into(),
&[sql::utils::get_nullifier_prefix(&nullifier1)],
)
.unwrap();
assert_eq!(
nullifiers,
vec![NullifierInfo {
nullifier: nullifier1,
block_num: block_number1
}]
);
let nullifiers = sql::select_nullifiers_by_block_range(
&mut conn,
0.into(),
u32::MAX.into(),
&[sql::utils::get_nullifier_prefix(&nullifier2)],
)
.unwrap();
assert_eq!(
nullifiers,
vec![NullifierInfo {
nullifier: nullifier2,
block_num: block_number2
}]
);

// Nullifiers created at block_end are included
let nullifiers = sql::select_nullifiers_by_block_range(
&mut conn,
0.into(),
1.into(),
&[
sql::utils::get_nullifier_prefix(&nullifier1),
sql::utils::get_nullifier_prefix(&nullifier2),
],
)
.unwrap();
assert_eq!(
nullifiers,
vec![NullifierInfo {
nullifier: nullifier1,
block_num: block_number1
}]
);

// Nullifiers created at block_start are not included
let nullifiers = sql::select_nullifiers_by_block_range(
&mut conn,
1.into(),
u32::MAX.into(),
&[
sql::utils::get_nullifier_prefix(&nullifier1),
sql::utils::get_nullifier_prefix(&nullifier2),
],
)
.unwrap();
assert_eq!(
nullifiers,
vec![NullifierInfo {
nullifier: nullifier2,
block_num: block_number2
}]
);

// When block start and end are the same, no nullifiers should be returned. This case happens
// when the client requests a sync update, and it is already tracking the chain tip.
let nullifiers = sql::select_nullifiers_by_block_range(
&mut conn,
2.into(),
2.into(),
&[
sql::utils::get_nullifier_prefix(&nullifier1),
sql::utils::get_nullifier_prefix(&nullifier2),
],
)
.unwrap();
assert!(nullifiers.is_empty());
}

#[test]
fn select_nullifiers_by_prefix() {
const PREFIX_LEN: u32 = 16;
Expand Down
Loading

0 comments on commit f0b01a4

Please sign in to comment.