forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rpc: add filter to getProgramAccounts (solana-labs#10888)
* Add RpcFilterType, and implement CompareBytes for getProgramAccounts * Accept bytes in bs58 * Rename to memcmp * Add Memcmp optional encoding field * Add dataSize filter * Update docs * Clippy * Simplify tests that don't need to test account contents; add multiple-filter tests
- Loading branch information
1 parent
7f2b117
commit 9207bdf
Showing
6 changed files
with
380 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use thiserror::Error; | ||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub enum RpcFilterType { | ||
DataSize(u64), | ||
Memcmp(Memcmp), | ||
} | ||
|
||
impl RpcFilterType { | ||
pub fn verify(&self) -> Result<(), RpcFilterError> { | ||
match self { | ||
RpcFilterType::DataSize(_) => Ok(()), | ||
RpcFilterType::Memcmp(compare) => { | ||
let encoding = compare.encoding.as_ref().unwrap_or(&MemcmpEncoding::Binary); | ||
match encoding { | ||
MemcmpEncoding::Binary => { | ||
let MemcmpEncodedBytes::Binary(bytes) = &compare.bytes; | ||
bs58::decode(&bytes) | ||
.into_vec() | ||
.map(|_| ()) | ||
.map_err(|e| e.into()) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Error, Debug)] | ||
pub enum RpcFilterError { | ||
#[error("bs58 decode error")] | ||
DecodeError(#[from] bs58::decode::Error), | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub enum MemcmpEncoding { | ||
Binary, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase", untagged)] | ||
pub enum MemcmpEncodedBytes { | ||
Binary(String), | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
pub struct Memcmp { | ||
/// Data offset to begin match | ||
pub offset: usize, | ||
/// Bytes, encoded with specified encoding, or default Binary | ||
pub bytes: MemcmpEncodedBytes, | ||
/// Optional encoding specification | ||
pub encoding: Option<MemcmpEncoding>, | ||
} | ||
|
||
impl Memcmp { | ||
pub fn bytes_match(&self, data: &[u8]) -> bool { | ||
match &self.bytes { | ||
MemcmpEncodedBytes::Binary(bytes) => { | ||
let bytes = bs58::decode(bytes).into_vec(); | ||
if bytes.is_err() { | ||
return false; | ||
} | ||
let bytes = bytes.unwrap(); | ||
if self.offset > data.len() { | ||
return false; | ||
} | ||
if data[self.offset..].len() < bytes.len() { | ||
return false; | ||
} | ||
data[self.offset..self.offset + bytes.len()] == bytes[..] | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_bytes_match() { | ||
let data = vec![1, 2, 3, 4, 5]; | ||
|
||
// Exact match of data succeeds | ||
assert!(Memcmp { | ||
offset: 0, | ||
bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![1, 2, 3, 4, 5]).into_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
|
||
// Partial match of data succeeds | ||
assert!(Memcmp { | ||
offset: 0, | ||
bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![1, 2]).into_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
|
||
// Offset partial match of data succeeds | ||
assert!(Memcmp { | ||
offset: 2, | ||
bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![3, 4]).into_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
|
||
// Incorrect partial match of data fails | ||
assert!(!Memcmp { | ||
offset: 0, | ||
bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![2]).into_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
|
||
// Bytes overrun data fails | ||
assert!(!Memcmp { | ||
offset: 2, | ||
bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![3, 4, 5, 6]).into_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
|
||
// Offset outside data fails | ||
assert!(!Memcmp { | ||
offset: 6, | ||
bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![5]).into_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
|
||
// Invalid base-58 fails | ||
assert!(!Memcmp { | ||
offset: 0, | ||
bytes: MemcmpEncodedBytes::Binary("III".to_string()), | ||
encoding: None, | ||
} | ||
.bytes_match(&data)); | ||
} | ||
} |
Oops, something went wrong.