Skip to content

Commit

Permalink
feat: allow matching with regex for stacks print_event (#380)
Browse files Browse the repository at this point in the history
### Description

This PR introduces the ability to match on a `print_event`'s values with
a regex string rather than just with the previous `contains` field.

Fixes #348 

### Example

This allows the following new `if_this` condition for a stacks
predicate:
```JSON
{
  "scope": "print_event",
  "contract_identifier": "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data",
  "regex": "... some regex to match on the event data"
}
```

---

### Checklist

- [x] All tests pass
- [x] Tests added in this PR (if applicable)
  • Loading branch information
MicaiahReid authored Aug 15, 2023
1 parent d688bcb commit 131809e
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 38 deletions.
33 changes: 26 additions & 7 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions components/chainhook-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
start_block: Some(34239),
end_block: Some(50000),
blocks: None,
predicate: StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
predicate: StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "ST1SVA0SST0EDT4MFYGWGP6GNSXMMQJDVP1G8QTTC.arkadiko-freddie-v1-1".into(),
contains: "vault".into(),
}),
Expand All @@ -355,7 +355,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
start_block: Some(34239),
end_block: Some(50000),
blocks: None,
predicate: StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
predicate: StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1".into(),
contains: "vault".into(),
}),
Expand Down
1 change: 1 addition & 0 deletions components/chainhook-cli/src/service/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ async fn it_handles_stacks_predicates_with_network(network: &str) {
#[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": "*", "matches_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
52 changes: 42 additions & 10 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, StacksPrintEventBasedPredicate,
};
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 @@ -406,16 +407,47 @@ pub fn evaluate_stacks_predicate_on_transaction<'a>(
match event {
StacksTransactionEvent::SmartContractEvent(actual) => {
if actual.topic == "print" {
if expected_event.contract_identifier == actual.contract_identifier
|| expected_event.contract_identifier == "*"
{
if expected_event.contains == "*" {
return true;
match expected_event {
StacksPrintEventBasedPredicate::Contains {
contract_identifier,
contains,
} => {
if contract_identifier == &actual.contract_identifier
|| contract_identifier == "*"
{
if contains == "*" {
return true;
}
let value = format!(
"{}",
expect_decoded_clarity_value(&actual.hex_value)
);
if value.contains(contains) {
return true;
}
}
}
let value =
format!("{}", expect_decoded_clarity_value(&actual.hex_value));
if value.contains(&expected_event.contains) {
return true;
StacksPrintEventBasedPredicate::MatchesRegex {
contract_identifier,
regex,
} => {
if contract_identifier == &actual.contract_identifier
|| contract_identifier == "*"
{
if let Ok(regex) = Regex::new(regex) {
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
42 changes: 35 additions & 7 deletions components/chainhook-sdk/src/chainhooks/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ pub mod fixtures;
// PrintEvent predicate tests
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "some-value".to_string()
}),
Expand All @@ -236,7 +236,7 @@ pub mod fixtures;
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_not_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "some-value".to_string(),
}),
Expand All @@ -245,7 +245,7 @@ pub mod fixtures;
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "wront-id".to_string(),
contains: "some-value".to_string(),
}),
Expand All @@ -254,7 +254,7 @@ pub mod fixtures;
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier:
"ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "wrong-value".to_string(),
Expand All @@ -264,7 +264,7 @@ pub mod fixtures;
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "*".to_string(),
contains: "some-value".to_string(),
}),
Expand All @@ -273,7 +273,7 @@ pub mod fixtures;
)]
#[test_case(
vec![vec![get_test_event_by_type("smart_contract_print_event")]],
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
contains: "*".to_string(),
}),
Expand All @@ -282,13 +282,41 @@ pub mod fixtures;
)]
#[test_case(
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 {
StacksPredicate::PrintEvent(StacksPrintEventBasedPredicate::Contains {
contract_identifier: "*".to_string(),
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::MatchesRegex {
contract_identifier: "ST3AXH4EBHD63FCFPTZ8GR29TNTVWDYPGY0KDY5E5.loan-data".to_string(),
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::MatchesRegex {
contract_identifier: "*".to_string(),
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::MatchesRegex {
contract_identifier: "*".to_string(),
regex: "[".to_string(),
}),
0
;
"PrintEvent predicate does not match invalid regex"
)]
fn test_stacks_predicates(
blocks_with_events: Vec<Vec<StacksTransactionEvent>>,
predicate: StacksPredicate,
Expand Down
14 changes: 11 additions & 3 deletions components/chainhook-sdk/src/chainhooks/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,9 +776,17 @@ pub enum StacksTrait {

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct StacksPrintEventBasedPredicate {
pub contract_identifier: String,
pub contains: String,
#[serde(untagged)]
pub enum StacksPrintEventBasedPredicate {
Contains {
contract_identifier: String,
contains: String,
},
MatchesRegex {
contract_identifier: String,
#[serde(rename = "matches_regex")]
regex: String,
},
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
Expand Down
42 changes: 33 additions & 9 deletions docs/chainhook-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"title": "chainhook",
"version": "0.17.0"
"version": "1.0.0"
},
"paths": {
"/ping": {
Expand Down Expand Up @@ -997,9 +997,39 @@
},
{
"type": "object",
"anyOf": [
{
"type": "object",
"required": [
"contains",
"contract_identifier"
],
"properties": {
"contract_identifier": {
"type": "string"
},
"contains": {
"type": "string"
}
}
},
{
"type": "object",
"required": [
"contract_identifier",
"matches_regex"
],
"properties": {
"contract_identifier": {
"type": "string"
},
"matches_regex": {
"type": "string"
}
}
}
],
"required": [
"contains",
"contract_identifier",
"scope"
],
"properties": {
Expand All @@ -1008,12 +1038,6 @@
"enum": [
"print_event"
]
},
"contract_identifier": {
"type": "string"
},
"contains": {
"type": "string"
}
}
},
Expand Down

0 comments on commit 131809e

Please sign in to comment.