Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING CHANGE: allow matching with regex for stacks print_event #379

Closed
wants to merge 3 commits into from
Closed
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
35 changes: 27 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions components/chainhook-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use chainhook_sdk::chainhooks::types::{
BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinPredicateType,
ChainhookFullSpecification, FileHook, HookAction, OrdinalOperations,
StacksChainhookFullSpecification, StacksChainhookNetworkSpecification, StacksPredicate,
StacksPrintEventBasedPredicate,
StacksPrintEventBasedPredicate, StringMatchingRule,
};
use chainhook_sdk::types::{BitcoinNetwork, BlockIdentifier, StacksNetwork};
use chainhook_sdk::utils::Context;
Expand Down Expand Up @@ -341,7 +341,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
blocks: None,
predicate: StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "ST1SVA0SST0EDT4MFYGWGP6GNSXMMQJDVP1G8QTTC.arkadiko-freddie-v1-1".into(),
contains: "vault".into(),
matching_rule: StringMatchingRule::Contains("vault".into()),
}),
expire_after_occurrence: None,
capture_all_events: None,
Expand All @@ -357,7 +357,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
blocks: None,
predicate: StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1".into(),
contains: "vault".into(),
matching_rule: StringMatchingRule::Contains("vault".into()),
}),
expire_after_occurrence: None,
capture_all_events: None,
Expand Down
9 changes: 5 additions & 4 deletions components/chainhook-cli/src/service/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,11 @@ async fn it_handles_stacks_predicates_with_network(network: &str) {
#[test_case(json!({"scope":"contract_deployment", "implement_trait": "sip10"}); "with scope contract_deployment type implement_trait sip10")]
#[test_case(json!({"scope":"contract_deployment", "implement_trait": "*"}); "with scope contract_deployment type implement_trait and wildcard trait")]
#[test_case(json!({"scope":"contract_call","contract_identifier": "SP000000000000000000002Q6VF78.pox","method": "stack-stx"}); "with scope contract_call")]
#[test_case(json!({"scope":"print_event","contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09","contains": "vault"}); "with scope print_event both fields")]
#[test_case(json!({"scope":"print_event","contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", "contains": "*"}); "with scope print_event wildcard conatins")]
#[test_case(json!({"scope":"print_event","contract_identifier": "*", "contains": "vault"}); "with scope print_event wildcard contract_identifier")]
#[test_case(json!({"scope":"print_event", "contract_identifier": "*", "contains": "*"}); "with scope print_event wildcard both fields")]
#[test_case(json!({"scope":"print_event","contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09","matching_rule": { "contains": "vault"}}); "with scope print_event both fields")]
#[test_case(json!({"scope":"print_event","contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09", "matching_rule": { "contains": "*"}}); "with scope print_event wildcard conatins")]
#[test_case(json!({"scope":"print_event","contract_identifier": "*", "matching_rule": { "contains": "vault" }}); "with scope print_event wildcard contract_identifier")]
#[test_case(json!({"scope":"print_event", "contract_identifier": "*", "matching_rule": { "contains": "*"}}); "with scope print_event wildcard both fields")]
#[test_case(json!({"scope":"print_event", "contract_identifier": "*", "matching_rule": { "regex": "(some)|(value)"}}); "with scope print_event and matching_rule regex")]
#[test_case(json!({"scope":"ft_event","asset_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.cbtc-token::cbtc","actions": ["burn"]}); "with scope ft_event")]
#[test_case(json!({"scope":"nft_event","asset_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.monkey-sip09::monkeys","actions": ["mint", "transfer", "burn"]}); "with scope nft_event")]
#[test_case(json!({"scope":"stx_event","actions": ["transfer", "lock"]}); "with scope stx_event")]
Expand Down
1 change: 1 addition & 0 deletions components/chainhook-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ zeromq = { version = "0.3.3", default-features = false, features = ["tokio-runti
dashmap = "5.4.0"
fxhash = "0.2.1"
lazy_static = "1.4.0"
regex = "1.9.3"

[dev-dependencies]
test-case = "3.1.0"
Expand Down
38 changes: 30 additions & 8 deletions components/chainhook-sdk/src/chainhooks/stacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use crate::utils::{AbstractStacksBlock, Context};

use super::types::{
BlockIdentifierIndexRule, ExactMatchingRule, HookAction, StacksChainhookSpecification,
StacksContractDeploymentPredicate, StacksPredicate,
StacksContractDeploymentPredicate, StacksPredicate, StringMatchingRule,
};
use chainhook_types::{
BlockIdentifier, StacksChainEvent, StacksTransactionData, StacksTransactionEvent,
StacksTransactionKind, TransactionIdentifier,
};
use hiro_system_kit::slog;
use regex::Regex;
use reqwest::{Client, Method};
use serde_json::Value as JsonValue;
use stacks_rpc_client::clarity::stacks_common::codec::StacksMessageCodec;
Expand Down Expand Up @@ -409,13 +410,34 @@ pub fn evaluate_stacks_predicate_on_transaction<'a>(
if expected_event.contract_identifier == actual.contract_identifier
|| expected_event.contract_identifier == "*"
{
if expected_event.contains == "*" {
return true;
}
let value =
format!("{}", expect_decoded_clarity_value(&actual.hex_value));
if value.contains(&expected_event.contains) {
return true;
match &expected_event.matching_rule {
StringMatchingRule::Contains(contains) => {
if contains == "*" {
return true;
}
let value = format!(
"{}",
expect_decoded_clarity_value(&actual.hex_value)
);
if value.contains(contains) {
return true;
}
}
StringMatchingRule::Regex(regex_str) => {
if let Ok(regex) = Regex::new(regex_str) {
let value = format!(
"{}",
expect_decoded_clarity_value(&actual.hex_value)
);
if regex.is_match(&value) {
return true;
}
} else {
ctx.try_log(|logger| {
slog::error!(logger, "unable to parse print_event matching rule as regex")
});
}
}
}
}
}
Expand Down
48 changes: 39 additions & 9 deletions components/chainhook-sdk/src/chainhooks/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use self::fixtures::get_all_event_types;

use super::{
stacks::{evaluate_stacks_chainhooks_on_chain_event, StacksTriggerChainhook, handle_stacks_hook_action, StacksChainhookOccurrence},
types::{StacksChainhookSpecification, StacksPrintEventBasedPredicate, StacksNftEventBasedPredicate, StacksFtEventBasedPredicate,StacksContractCallBasedPredicate,StacksContractDeploymentPredicate, ExactMatchingRule, FileHook, StacksTrait},
types::{StacksChainhookSpecification, StacksPrintEventBasedPredicate, StacksNftEventBasedPredicate, StacksFtEventBasedPredicate,StacksContractCallBasedPredicate,StacksContractDeploymentPredicate, ExactMatchingRule, FileHook, StacksTrait, StringMatchingRule},
};
use crate::{chainhooks::{types::{HookAction, StacksPredicate, StacksStxEventBasedPredicate,}, tests::fixtures::{get_expected_occurrence, get_test_event_by_type}}, utils::AbstractStacksBlock};
use crate::utils::Context;
Expand Down Expand Up @@ -217,7 +217,7 @@ pub mod fixtures;
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "some-value".to_string()
matching_rule: StringMatchingRule::Contains("some-value".to_string()),
}),
1;
"PrintEvent predicate matches contract_identifier and contains"
Expand All @@ -226,16 +226,16 @@ pub mod fixtures;
vec![vec![get_test_event_by_type("smart_contract_not_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "some-value".to_string(),
matching_rule: StringMatchingRule::Contains("some-value".to_string()),
}),
0;
"PrintEvent predicate does not check events with topic other than print"
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "wront-id".to_string(),
contains: "some-value".to_string(),
contract_identifier: "wrong-id".to_string(),
matching_rule: StringMatchingRule::Contains("some-value".to_string()),
}),
0;
"PrintEvent predicate rejects non matching contract_identifier"
Expand All @@ -245,7 +245,7 @@ pub mod fixtures;
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier:
"ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "wrong-value".to_string(),
matching_rule: StringMatchingRule::Contains("wrong-value".to_string()),
}),
0;
"PrintEvent predicate rejects non matching contains value"
Expand All @@ -254,7 +254,7 @@ pub mod fixtures;
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "*".to_string(),
contains: "some-value".to_string(),
matching_rule: StringMatchingRule::Contains("some-value".to_string()),
}),
1;
"PrintEvent predicate contract_identifier wildcard checks all print events for match"
Expand All @@ -263,7 +263,7 @@ pub mod fixtures;
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "*".to_string(),
matching_rule: StringMatchingRule::Contains("*".to_string()),
}),
1;
"PrintEvent predicate contains wildcard matches all values for matching events"
Expand All @@ -272,11 +272,40 @@ pub mod fixtures;
vec![vec![get_test_event_by_type("smart_contract_print_event")], vec![get_test_event_by_type("smart_contract_print_event_empty")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "*".to_string(),
contains: "*".to_string(),
matching_rule: StringMatchingRule::Contains("*".to_string()),
}),
2;
"PrintEvent predicate contract_identifier wildcard and contains wildcard matches all values on all print events"
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
matching_rule: StringMatchingRule::Regex("(some)|(value)".to_string()),
}),
1;
"PrintEvent predicate matches contract_identifier and regex"
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "*".to_string(),
matching_rule: StringMatchingRule::Regex("(some)|(value)".to_string()),
}),
1;
"PrintEvent predicate contract_identifier wildcard checks all print events for match with regex"
)]

#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
contract_identifier: "*".to_string(),
matching_rule: StringMatchingRule::Regex("[".to_string()),
}),
0
;
"PrintEvent predicate does not match invalid regex"
)]
fn test_stacks_predicates(blocks_with_events: Vec<Vec<StacksTransactionEvent>>, predicate: StacksPredicate, expected_applies: u64) {
// Prepare block
let new_blocks = blocks_with_events.iter().map(|events| StacksBlockUpdate {
Expand Down Expand Up @@ -320,6 +349,7 @@ fn test_stacks_predicates(blocks_with_events: Vec<Vec<StacksTransactionEvent>>,
}



#[test_case(
StacksPredicate::ContractDeployment(StacksContractDeploymentPredicate::Deployer("ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9".to_string())),
1;
Expand Down
9 changes: 8 additions & 1 deletion components/chainhook-sdk/src/chainhooks/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,13 @@ pub enum Scope {
Outputs,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum StringMatchingRule {
Contains(String),
Regex(String),
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum MatchingRule {
Expand Down Expand Up @@ -778,7 +785,7 @@ pub enum StacksTrait {
#[serde(rename_all = "snake_case")]
pub struct StacksPrintEventBasedPredicate {
pub contract_identifier: String,
pub contains: String,
pub matching_rule: StringMatchingRule,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
Expand Down
Loading