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

DPLT-980 Tests for wildcard matching #86

Merged
merged 34 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1c8d90c
Utility to fetch blocks for tests. Wildcard tests for Coordinator ind…
gabehamilton May 31, 2023
3268dfa
Allow snake case blocks to be returned from block server
gabehamilton May 31, 2023
4dd1935
feat: Stream blocks while executing (#87)
roshaans Jun 1, 2023
4296b11
[DPLT-1019] feat: store debug list in LocalStorage (#90)
roshaans Jun 2, 2023
7485e5b
DPLT-929 historical filtering (#81)
gabehamilton Jun 2, 2023
956d41b
ci: Fix rust CI check workflow
morgsmccauley Jun 1, 2023
1342f23
[DPLT-1009] feat: contract validation with wild cards (#93)
roshaans Jun 5, 2023
0c62837
fix: contract name
roshaans Jun 5, 2023
311cd92
[DPLT-1007] feat: Add link to QueryApi Docs (#95)
roshaans Jun 5, 2023
0e162fb
DPLT-1002 Publish current block height of indexer functions to CloudW…
morgsmccauley Jun 6, 2023
3d995e8
DPLT-1020 Fetch indexing metadata file to determine last_indexed_bloc…
gabehamilton Jun 6, 2023
60ac30f
Fixed timestamp field for lag log.
gabehamilton Jun 6, 2023
4ffa492
Refactor: Introduce Editor context refactoring (#98)
roshaans Jun 12, 2023
4d4905f
Updated indexed historical data folder
gabehamilton Jun 12, 2023
b8dc5bb
Limit unindexed block processing to two hours of blocks.
gabehamilton Jun 12, 2023
3e064b3
[DPLT-1021] feat: enable graphiql explorer plugin (#94)
roshaans Jun 12, 2023
27d134d
DPLT-1028 feat: Fork Your Own indexer modal (#99)
roshaans Jun 13, 2023
fd08139
DPLT-936 message per block (#101)
gabehamilton Jun 14, 2023
7c33b12
Shell script for test blocks, updated test for block level matching r…
gabehamilton Jun 26, 2023
d61bc9e
cargo fmt
gabehamilton Jun 26, 2023
3eb0009
Updated readme with test block info.
gabehamilton Jun 26, 2023
f0ab72d
Merge branch 'main' into DPLT-980_wildcard_tests
gabehamilton Jun 26, 2023
01f0ddf
Fixed merge error
gabehamilton Jun 26, 2023
b6f305a
Refactor S3 and SQS operations into their own modules.
gabehamilton Jun 26, 2023
b80f90f
S3 list operation now handles continuation tokens and directory listi…
gabehamilton Jun 27, 2023
d1e3e57
Small adjustments from PR feedback
gabehamilton Jun 27, 2023
0ed136e
S3 list methods for wildcards and comma separated contracts.
gabehamilton Jun 28, 2023
851979c
Full support for wildcard and CSV contract matching.
gabehamilton Jun 28, 2023
421f07d
Merge pull request #111 from near/DPLT-980_wildcard_and_csv
gabehamilton Jun 28, 2023
4bec534
Merge pull request #110 from near/DPLT-980_s3_list_operation
gabehamilton Jun 28, 2023
f226866
Merge pull request #108 from near/DPLT-980_aws_refactor
gabehamilton Jun 28, 2023
da4e5a3
Merge branch 'main' into DPLT-980_wildcard_tests
gabehamilton Jun 28, 2023
38fb20e
Clippy recommended fixes
gabehamilton Jun 28, 2023
05bcc3a
The fmt of the clippy
gabehamilton Jun 28, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.env*
redis/
*.log
/indexer/blocks/
Copy link
Collaborator

Choose a reason for hiding this comment

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

The tests explicitly depend on this data, should we just commit it to the repo?

29 changes: 19 additions & 10 deletions block-server/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ module.exports.block = async (event) => {
try {
// parse request params
const { block_height } = event.pathParameters;
const block = await fetchStreamerMessage(block_height);
const options = event.queryStringParameters || {};
options.snake_case = options.snake_case === 'true';
const block = await fetchStreamerMessage(block_height, options);
return {
statusCode: 200,
headers,
Expand All @@ -32,10 +34,10 @@ const normalizeBlockHeight = function(block_height) {
return block_height.toString().padStart(12, '0');
}

const fetchStreamerMessage = async function(block_height) {
const blockPromise = fetchBlockPromise(block_height);
const fetchStreamerMessage = async function(block_height, options) {
const blockPromise = fetchBlockPromise(block_height, options);
// hardcoding 4 shards to test performance
const shardsPromises = await fetchShardsPromises(block_height, 4); // block.chunks.length)
const shardsPromises = await fetchShardsPromises(block_height, 4, options); // block.chunks.length)

const results = await Promise.all([blockPromise, ...shardsPromises]);
const block = results.shift();
Expand All @@ -46,30 +48,37 @@ const fetchStreamerMessage = async function(block_height) {
};
}

const fetchShardsPromises = async function(block_height, number_of_shards) {
const fetchShardsPromises = async function(block_height, number_of_shards, options) {
return ([...Array(number_of_shards).keys()].map((shard_id) =>
fetchShardPromise(block_height, shard_id)));
fetchShardPromise(block_height, shard_id, options)));
}

const fetchShardPromise = function(block_height, shard_id) {
const fetchShardPromise = function(block_height, shard_id, options) {
const params = {
Bucket: `near-lake-data-${NETWORK}`,
Key: `${normalizeBlockHeight(block_height)}/shard_${shard_id}.json`,
};
return S3.getObject(params).promise().then((response) => {
return JSON.parse(response.Body.toString(), (key, value) => renameUnderscoreFieldsToCamelCase(value));
return JSON.parse(response.Body.toString(), (key, value) => {
if(options.snake_case) return value
return renameUnderscoreFieldsToCamelCase(value)
});
});
}

const fetchBlockPromise = function(block_height) {
const fetchBlockPromise = function(block_height, options) {
const file = 'block.json';
const folder = normalizeBlockHeight(block_height);
const params = {
Bucket: 'near-lake-data-' + NETWORK,
Key: `${folder}/${file}`,
};
return S3.getObject(params).promise().then((response) => {
const block = JSON.parse(response.Body.toString(), (key, value) => renameUnderscoreFieldsToCamelCase(value));
const block = JSON.parse(response.Body.toString(), (key, value) => {
if(options.snake_case) return value
return renameUnderscoreFieldsToCamelCase(value)
});

return block;
});
}
Expand Down
2 changes: 2 additions & 0 deletions indexer/Cargo.lock

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

15 changes: 11 additions & 4 deletions indexer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ This project is using `workspace` feature of Cargo.

### Crates

- [`alert-rules`](./alert-rules) crate provides the `AlertRule` type for usage in other crates
- [`shared`](./shared) crate holds the common `clap` structs for every indexer in the workspace. Also, it includes shared types and utils.
- [`storage`](./storage) crate provides the functions to work with Redis that are common for all indexers in the workspace
- [`indexer_rule_type`](./indexer_rule_type) provides the IndexerRule type to this app and the registry contract.
- [`indexer_rules_engine`](./indexer_rules_engine) contains logic for matching IndexerRules against StreamerMessages
- [`storage`](./storage) crate provides the functions to work with Redis

### Indexers

- [`queryapi_coordinator`](./queryapi_coordinator) an indexer to watch for `AlertRules` and index changes to the QueryApi registry contract.
- [`queryapi_coordinator`](./queryapi_coordinator) an indexer to index changes to the QueryApi registry contract and
to watch for `IndexerRules` associated with the IndexerFunctions in the registry.

### Tests
Some tests require blocks with matching data. To download the test block, run
`./download_test_blocks.sh 93085141`. Some other useful blocks are 80854399 92476362 93085141 93659695.

To log a message instead of sending SQS messages set your `QUEUE_URL` to `MOCK` in your `.env` file.

## Design concept

Expand Down
9 changes: 9 additions & 0 deletions indexer/download_test_blocks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

mkdir -p ./blocks

# Iterate over all script arguments
for block_id in "$@"
do
curl -o "./blocks/${block_id}.json" "https://70jshyr5cb.execute-api.eu-central-1.amazonaws.com/block/${block_id}?snake_case=true"
done
3 changes: 3 additions & 0 deletions indexer/indexer_rules_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ wildmatch = "2.1.1"

near-lake-framework = "0.7.1"
indexer_rule_type = { path = "../indexer_rule_type" }

[dev-dependencies]
tokio = { version = "1.0.1", features = ["full"] }
13 changes: 10 additions & 3 deletions indexer/indexer_rules_engine/src/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,16 @@ fn match_account(
account_id: &str,
outcome_with_receipt: &IndexerExecutionOutcomeWithReceipt,
) -> bool {
wildmatch::WildMatch::new(account_id).matches(&outcome_with_receipt.receipt.receiver_id)
|| wildmatch::WildMatch::new(account_id)
.matches(&outcome_with_receipt.receipt.predecessor_id)
match account_id {
x if x.contains(",") => x
.split(",")
.any(|sub_account_id| match_account(sub_account_id.trim(), outcome_with_receipt)),
_ => {
wildmatch::WildMatch::new(account_id).matches(&outcome_with_receipt.receipt.receiver_id)
|| wildmatch::WildMatch::new(account_id)
.matches(&outcome_with_receipt.receipt.predecessor_id)
}
}
}

fn match_status(status: &Status, execution_outcome_status: &ExecutionStatusView) -> bool {
Expand Down
157 changes: 157 additions & 0 deletions indexer/indexer_rules_engine/src/outcomes_reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,160 @@ fn build_indexer_rule_match_payload(
}
}
}

#[cfg(test)]
mod tests {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we also test for a wildcard which doesn't match anything to avoid false positives here?

use crate::outcomes_reducer::reduce_indexer_rule_matches_from_outcomes;
use crate::types::indexer_rule_match::{ChainId, IndexerRuleMatch};
use indexer_rule_type::indexer_rule::{IndexerRule, IndexerRuleKind, MatchingRule, Status};
use near_lake_framework::near_indexer_primitives::StreamerMessage;

fn read_local_file(path: &str) -> String {
std::fs::read_to_string(path).unwrap()
}
fn read_local_streamer_message(block_height: u64) -> StreamerMessage {
let path = format!("../blocks/{}.json", block_height);
let json = serde_json::from_str(&read_local_file(&path)).unwrap();
serde_json::from_value(json).unwrap()
}

#[tokio::test]
async fn match_wildcard_no_match() {
let wildcard_rule = IndexerRule {
indexer_rule_kind: IndexerRuleKind::Action,
matching_rule: MatchingRule::ActionAny {
affected_account_id: "*.nearcrow.near".to_string(),
status: Status::Success,
},
id: None,
name: None,
};

let streamer_message = read_local_streamer_message(93085141);
let result: Vec<IndexerRuleMatch> = reduce_indexer_rule_matches_from_outcomes(
&wildcard_rule,
&streamer_message,
ChainId::Testnet,
)
.await
.unwrap();

assert_eq!(result.len(), 0);
}

#[tokio::test]
async fn match_wildcard_contract_subaccount_name() {
let wildcard_rule = IndexerRule {
indexer_rule_kind: IndexerRuleKind::Action,
matching_rule: MatchingRule::ActionAny {
affected_account_id: "*.nearcrowd.near".to_string(),
status: Status::Success,
},
id: None,
name: None,
};

let streamer_message = read_local_streamer_message(93085141);
let result: Vec<IndexerRuleMatch> = reduce_indexer_rule_matches_from_outcomes(
&wildcard_rule,
&streamer_message,
ChainId::Testnet,
)
.await
.unwrap();

assert_eq!(result.len(), 1); // There are two matches, until we add Extraction we are just matching the first one (block matching)
}

#[tokio::test]
async fn match_wildcard_mid_contract_name() {
let wildcard_rule = IndexerRule {
indexer_rule_kind: IndexerRuleKind::Action,
matching_rule: MatchingRule::ActionAny {
affected_account_id: "*crowd.near".to_string(),
status: Status::Success,
},
id: None,
name: None,
};

let streamer_message = read_local_streamer_message(93085141);
let result: Vec<IndexerRuleMatch> = reduce_indexer_rule_matches_from_outcomes(
&wildcard_rule,
&streamer_message,
ChainId::Testnet,
)
.await
.unwrap();

assert_eq!(result.len(), 1); // see Extraction note in previous test

let wildcard_rule = IndexerRule {
indexer_rule_kind: IndexerRuleKind::Action,
matching_rule: MatchingRule::ActionAny {
affected_account_id: "app.nea*owd.near".to_string(),
status: Status::Success,
},
id: None,
name: None,
};

let result: Vec<IndexerRuleMatch> = reduce_indexer_rule_matches_from_outcomes(
&wildcard_rule,
&streamer_message,
ChainId::Testnet,
)
.await
.unwrap();

assert_eq!(result.len(), 1); // see Extraction note in previous test
}

#[tokio::test]
async fn match_csv_account() {
let wildcard_rule = IndexerRule {
indexer_rule_kind: IndexerRuleKind::Action,
matching_rule: MatchingRule::ActionAny {
affected_account_id: "notintheblockaccount.near, app.nearcrowd.near".to_string(),
status: Status::Success,
},
id: None,
name: None,
};

let streamer_message = read_local_streamer_message(93085141);
let result: Vec<IndexerRuleMatch> = reduce_indexer_rule_matches_from_outcomes(
&wildcard_rule,
&streamer_message,
ChainId::Testnet,
)
.await
.unwrap();

assert_eq!(result.len(), 1); // There are two matches, until we add Extraction we are just matching the first one (block matching)
}

#[tokio::test]
async fn match_csv_wildcard_account() {
let wildcard_rule = IndexerRule {
indexer_rule_kind: IndexerRuleKind::Action,
matching_rule: MatchingRule::ActionAny {
affected_account_id: "notintheblockaccount.near, *.nearcrowd.near".to_string(),
status: Status::Success,
},
id: None,
name: None,
};

let streamer_message = read_local_streamer_message(93085141);
let result: Vec<IndexerRuleMatch> = reduce_indexer_rule_matches_from_outcomes(
&wildcard_rule,
&streamer_message,
ChainId::Testnet,
)
.await
.unwrap();

assert_eq!(result.len(), 1); // There are two matches, until we add Extraction we are just matching the first one (block matching)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ impl IndexerRuleMatch {
Clone,
Debug,
)]
// todo add block height
pub enum IndexerRuleMatchPayload {
Actions {
block_hash: BlockHashString,
Expand Down
7 changes: 5 additions & 2 deletions indexer/queryapi_coordinator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ near-jsonrpc-client = "0.5.1"
near-jsonrpc-primitives = "0.16.0"
near-lake-framework = "0.7.1"
mockall = "0.9.1"
regex = "1"

# opts
base64 = "0.13.0"
clap = { version = "3.1.6", features = ["derive", "env"] }
dotenv = "0.15.0"
tracing-subscriber = "0.2.4"
unescape = "0.1.0"

# aws
aws-types = "0.53.0"
aws-credential-types = "0.53.0"
aws-sdk-s3 = "0.23.0"
aws-sdk-sqs = "0.23.0"
tracing-subscriber = "0.2.4"
unescape = "0.1.0"
Loading