You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

551 lines
14 KiB

abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
7 years ago
7 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
7 years ago
7 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
7 years ago
7 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
7 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
  1. package kv
  2. import (
  3. "bytes"
  4. "encoding/hex"
  5. "fmt"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/pkg/errors"
  11. dbm "github.com/tendermint/tm-db"
  12. cmn "github.com/tendermint/tendermint/libs/common"
  13. "github.com/tendermint/tendermint/libs/pubsub/query"
  14. "github.com/tendermint/tendermint/state/txindex"
  15. "github.com/tendermint/tendermint/types"
  16. )
  17. const (
  18. tagKeySeparator = "/"
  19. )
  20. var _ txindex.TxIndexer = (*TxIndex)(nil)
  21. // TxIndex is the simplest possible indexer, backed by key-value storage (levelDB).
  22. type TxIndex struct {
  23. store dbm.DB
  24. tagsToIndex []string
  25. indexAllTags bool
  26. }
  27. // NewTxIndex creates new KV indexer.
  28. func NewTxIndex(store dbm.DB, options ...func(*TxIndex)) *TxIndex {
  29. txi := &TxIndex{store: store, tagsToIndex: make([]string, 0), indexAllTags: false}
  30. for _, o := range options {
  31. o(txi)
  32. }
  33. return txi
  34. }
  35. // IndexTags is an option for setting which tags to index.
  36. func IndexTags(tags []string) func(*TxIndex) {
  37. return func(txi *TxIndex) {
  38. txi.tagsToIndex = tags
  39. }
  40. }
  41. // IndexAllTags is an option for indexing all tags.
  42. func IndexAllTags() func(*TxIndex) {
  43. return func(txi *TxIndex) {
  44. txi.indexAllTags = true
  45. }
  46. }
  47. // Get gets transaction from the TxIndex storage and returns it or nil if the
  48. // transaction is not found.
  49. func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) {
  50. if len(hash) == 0 {
  51. return nil, txindex.ErrorEmptyHash
  52. }
  53. rawBytes := txi.store.Get(hash)
  54. if rawBytes == nil {
  55. return nil, nil
  56. }
  57. txResult := new(types.TxResult)
  58. err := cdc.UnmarshalBinaryBare(rawBytes, &txResult)
  59. if err != nil {
  60. return nil, fmt.Errorf("Error reading TxResult: %v", err)
  61. }
  62. return txResult, nil
  63. }
  64. // AddBatch indexes a batch of transactions using the given list of events. Each
  65. // key that indexed from the tx's events is a composite of the event type and
  66. // the respective attribute's key delimited by a "." (eg. "account.number").
  67. // Any event with an empty type is not indexed.
  68. func (txi *TxIndex) AddBatch(b *txindex.Batch) error {
  69. storeBatch := txi.store.NewBatch()
  70. defer storeBatch.Close()
  71. for _, result := range b.Ops {
  72. hash := result.Tx.Hash()
  73. // index tx by events
  74. txi.indexEvents(result, hash, storeBatch)
  75. // index tx by height
  76. if txi.indexAllTags || cmn.StringInSlice(types.TxHeightKey, txi.tagsToIndex) {
  77. storeBatch.Set(keyForHeight(result), hash)
  78. }
  79. // index tx by hash
  80. rawBytes, err := cdc.MarshalBinaryBare(result)
  81. if err != nil {
  82. return err
  83. }
  84. storeBatch.Set(hash, rawBytes)
  85. }
  86. storeBatch.Write()
  87. return nil
  88. }
  89. // Index indexes a single transaction using the given list of events. Each key
  90. // that indexed from the tx's events is a composite of the event type and the
  91. // respective attribute's key delimited by a "." (eg. "account.number").
  92. // Any event with an empty type is not indexed.
  93. func (txi *TxIndex) Index(result *types.TxResult) error {
  94. b := txi.store.NewBatch()
  95. defer b.Close()
  96. hash := result.Tx.Hash()
  97. // index tx by events
  98. txi.indexEvents(result, hash, b)
  99. // index tx by height
  100. if txi.indexAllTags || cmn.StringInSlice(types.TxHeightKey, txi.tagsToIndex) {
  101. b.Set(keyForHeight(result), hash)
  102. }
  103. // index tx by hash
  104. rawBytes, err := cdc.MarshalBinaryBare(result)
  105. if err != nil {
  106. return err
  107. }
  108. b.Set(hash, rawBytes)
  109. b.Write()
  110. return nil
  111. }
  112. func (txi *TxIndex) indexEvents(result *types.TxResult, hash []byte, store dbm.SetDeleter) {
  113. for _, event := range result.Result.Events {
  114. // only index events with a non-empty type
  115. if len(event.Type) == 0 {
  116. continue
  117. }
  118. for _, attr := range event.Attributes {
  119. if len(attr.Key) == 0 {
  120. continue
  121. }
  122. compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key))
  123. if txi.indexAllTags || cmn.StringInSlice(compositeTag, txi.tagsToIndex) {
  124. store.Set(keyForEvent(compositeTag, attr.Value, result), hash)
  125. }
  126. }
  127. }
  128. }
  129. // Search performs a search using the given query. It breaks the query into
  130. // conditions (like "tx.height > 5"). For each condition, it queries the DB
  131. // index. One special use cases here: (1) if "tx.hash" is found, it returns tx
  132. // result for it (2) for range queries it is better for the client to provide
  133. // both lower and upper bounds, so we are not performing a full scan. Results
  134. // from querying indexes are then intersected and returned to the caller.
  135. func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) {
  136. var hashesInitialized bool
  137. filteredHashes := make(map[string][]byte)
  138. // get a list of conditions (like "tx.height > 5")
  139. conditions := q.Conditions()
  140. // if there is a hash condition, return the result immediately
  141. hash, err, ok := lookForHash(conditions)
  142. if err != nil {
  143. return nil, errors.Wrap(err, "error during searching for a hash in the query")
  144. } else if ok {
  145. res, err := txi.Get(hash)
  146. if res == nil {
  147. return []*types.TxResult{}, nil
  148. }
  149. return []*types.TxResult{res}, errors.Wrap(err, "error while retrieving the result")
  150. }
  151. // conditions to skip because they're handled before "everything else"
  152. skipIndexes := make([]int, 0)
  153. // extract ranges
  154. // if both upper and lower bounds exist, it's better to get them in order not
  155. // no iterate over kvs that are not within range.
  156. ranges, rangeIndexes := lookForRanges(conditions)
  157. if len(ranges) > 0 {
  158. skipIndexes = append(skipIndexes, rangeIndexes...)
  159. for _, r := range ranges {
  160. if !hashesInitialized {
  161. filteredHashes = txi.matchRange(r, startKey(r.key), filteredHashes, true)
  162. hashesInitialized = true
  163. // Ignore any remaining conditions if the first condition resulted
  164. // in no matches (assuming implicit AND operand).
  165. if len(filteredHashes) == 0 {
  166. break
  167. }
  168. } else {
  169. filteredHashes = txi.matchRange(r, startKey(r.key), filteredHashes, false)
  170. }
  171. }
  172. }
  173. // if there is a height condition ("tx.height=3"), extract it
  174. height := lookForHeight(conditions)
  175. // for all other conditions
  176. for i, c := range conditions {
  177. if cmn.IntInSlice(i, skipIndexes) {
  178. continue
  179. }
  180. if !hashesInitialized {
  181. filteredHashes = txi.match(c, startKeyForCondition(c, height), filteredHashes, true)
  182. hashesInitialized = true
  183. // Ignore any remaining conditions if the first condition resulted
  184. // in no matches (assuming implicit AND operand).
  185. if len(filteredHashes) == 0 {
  186. break
  187. }
  188. } else {
  189. filteredHashes = txi.match(c, startKeyForCondition(c, height), filteredHashes, false)
  190. }
  191. }
  192. results := make([]*types.TxResult, 0, len(filteredHashes))
  193. for _, h := range filteredHashes {
  194. res, err := txi.Get(h)
  195. if err != nil {
  196. return nil, errors.Wrapf(err, "failed to get Tx{%X}", h)
  197. }
  198. results = append(results, res)
  199. }
  200. // sort by height & index by default
  201. sort.Slice(results, func(i, j int) bool {
  202. if results[i].Height == results[j].Height {
  203. return results[i].Index < results[j].Index
  204. }
  205. return results[i].Height < results[j].Height
  206. })
  207. return results, nil
  208. }
  209. func lookForHash(conditions []query.Condition) (hash []byte, err error, ok bool) {
  210. for _, c := range conditions {
  211. if c.Tag == types.TxHashKey {
  212. decoded, err := hex.DecodeString(c.Operand.(string))
  213. return decoded, err, true
  214. }
  215. }
  216. return
  217. }
  218. // lookForHeight returns a height if there is an "height=X" condition.
  219. func lookForHeight(conditions []query.Condition) (height int64) {
  220. for _, c := range conditions {
  221. if c.Tag == types.TxHeightKey && c.Op == query.OpEqual {
  222. return c.Operand.(int64)
  223. }
  224. }
  225. return 0
  226. }
  227. // special map to hold range conditions
  228. // Example: account.number => queryRange{lowerBound: 1, upperBound: 5}
  229. type queryRanges map[string]queryRange
  230. type queryRange struct {
  231. key string
  232. lowerBound interface{} // int || time.Time
  233. includeLowerBound bool
  234. upperBound interface{} // int || time.Time
  235. includeUpperBound bool
  236. }
  237. func (r queryRange) lowerBoundValue() interface{} {
  238. if r.lowerBound == nil {
  239. return nil
  240. }
  241. if r.includeLowerBound {
  242. return r.lowerBound
  243. } else {
  244. switch t := r.lowerBound.(type) {
  245. case int64:
  246. return t + 1
  247. case time.Time:
  248. return t.Unix() + 1
  249. default:
  250. panic("not implemented")
  251. }
  252. }
  253. }
  254. func (r queryRange) AnyBound() interface{} {
  255. if r.lowerBound != nil {
  256. return r.lowerBound
  257. } else {
  258. return r.upperBound
  259. }
  260. }
  261. func (r queryRange) upperBoundValue() interface{} {
  262. if r.upperBound == nil {
  263. return nil
  264. }
  265. if r.includeUpperBound {
  266. return r.upperBound
  267. } else {
  268. switch t := r.upperBound.(type) {
  269. case int64:
  270. return t - 1
  271. case time.Time:
  272. return t.Unix() - 1
  273. default:
  274. panic("not implemented")
  275. }
  276. }
  277. }
  278. func lookForRanges(conditions []query.Condition) (ranges queryRanges, indexes []int) {
  279. ranges = make(queryRanges)
  280. for i, c := range conditions {
  281. if isRangeOperation(c.Op) {
  282. r, ok := ranges[c.Tag]
  283. if !ok {
  284. r = queryRange{key: c.Tag}
  285. }
  286. switch c.Op {
  287. case query.OpGreater:
  288. r.lowerBound = c.Operand
  289. case query.OpGreaterEqual:
  290. r.includeLowerBound = true
  291. r.lowerBound = c.Operand
  292. case query.OpLess:
  293. r.upperBound = c.Operand
  294. case query.OpLessEqual:
  295. r.includeUpperBound = true
  296. r.upperBound = c.Operand
  297. }
  298. ranges[c.Tag] = r
  299. indexes = append(indexes, i)
  300. }
  301. }
  302. return ranges, indexes
  303. }
  304. func isRangeOperation(op query.Operator) bool {
  305. switch op {
  306. case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual:
  307. return true
  308. default:
  309. return false
  310. }
  311. }
  312. // match returns all matching txs by hash that meet a given condition and start
  313. // key. An already filtered result (filteredHashes) is provided such that any
  314. // non-intersecting matches are removed.
  315. //
  316. // NOTE: filteredHashes may be empty if no previous condition has matched.
  317. func (txi *TxIndex) match(c query.Condition, startKeyBz []byte, filteredHashes map[string][]byte, firstRun bool) map[string][]byte {
  318. // A previous match was attempted but resulted in no matches, so we return
  319. // no matches (assuming AND operand).
  320. if !firstRun && len(filteredHashes) == 0 {
  321. return filteredHashes
  322. }
  323. tmpHashes := make(map[string][]byte)
  324. switch {
  325. case c.Op == query.OpEqual:
  326. it := dbm.IteratePrefix(txi.store, startKeyBz)
  327. defer it.Close()
  328. for ; it.Valid(); it.Next() {
  329. tmpHashes[string(it.Value())] = it.Value()
  330. }
  331. case c.Op == query.OpContains:
  332. // XXX: startKey does not apply here.
  333. // For example, if startKey = "account.owner/an/" and search query = "account.owner CONTAINS an"
  334. // we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/"
  335. it := dbm.IteratePrefix(txi.store, startKey(c.Tag))
  336. defer it.Close()
  337. for ; it.Valid(); it.Next() {
  338. if !isTagKey(it.Key()) {
  339. continue
  340. }
  341. if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) {
  342. tmpHashes[string(it.Value())] = it.Value()
  343. }
  344. }
  345. default:
  346. panic("other operators should be handled already")
  347. }
  348. if len(tmpHashes) == 0 || firstRun {
  349. // Either:
  350. //
  351. // 1. Regardless if a previous match was attempted, which may have had
  352. // results, but no match was found for the current condition, then we
  353. // return no matches (assuming AND operand).
  354. //
  355. // 2. A previous match was not attempted, so we return all results.
  356. return tmpHashes
  357. }
  358. // Remove/reduce matches in filteredHashes that were not found in this
  359. // match (tmpHashes).
  360. for k := range filteredHashes {
  361. if tmpHashes[k] == nil {
  362. delete(filteredHashes, k)
  363. }
  364. }
  365. return filteredHashes
  366. }
  367. // matchRange returns all matching txs by hash that meet a given queryRange and
  368. // start key. An already filtered result (filteredHashes) is provided such that
  369. // any non-intersecting matches are removed.
  370. //
  371. // NOTE: filteredHashes may be empty if no previous condition has matched.
  372. func (txi *TxIndex) matchRange(r queryRange, startKey []byte, filteredHashes map[string][]byte, firstRun bool) map[string][]byte {
  373. // A previous match was attempted but resulted in no matches, so we return
  374. // no matches (assuming AND operand).
  375. if !firstRun && len(filteredHashes) == 0 {
  376. return filteredHashes
  377. }
  378. tmpHashes := make(map[string][]byte)
  379. lowerBound := r.lowerBoundValue()
  380. upperBound := r.upperBoundValue()
  381. it := dbm.IteratePrefix(txi.store, startKey)
  382. defer it.Close()
  383. LOOP:
  384. for ; it.Valid(); it.Next() {
  385. if !isTagKey(it.Key()) {
  386. continue
  387. }
  388. if _, ok := r.AnyBound().(int64); ok {
  389. v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
  390. if err != nil {
  391. continue LOOP
  392. }
  393. include := true
  394. if lowerBound != nil && v < lowerBound.(int64) {
  395. include = false
  396. }
  397. if upperBound != nil && v > upperBound.(int64) {
  398. include = false
  399. }
  400. if include {
  401. tmpHashes[string(it.Value())] = it.Value()
  402. }
  403. // XXX: passing time in a ABCI Tags is not yet implemented
  404. // case time.Time:
  405. // v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
  406. // if v == r.upperBound {
  407. // break
  408. // }
  409. }
  410. }
  411. if len(tmpHashes) == 0 || firstRun {
  412. // Either:
  413. //
  414. // 1. Regardless if a previous match was attempted, which may have had
  415. // results, but no match was found for the current condition, then we
  416. // return no matches (assuming AND operand).
  417. //
  418. // 2. A previous match was not attempted, so we return all results.
  419. return tmpHashes
  420. }
  421. // Remove/reduce matches in filteredHashes that were not found in this
  422. // match (tmpHashes).
  423. for k := range filteredHashes {
  424. if tmpHashes[k] == nil {
  425. delete(filteredHashes, k)
  426. }
  427. }
  428. return filteredHashes
  429. }
  430. ///////////////////////////////////////////////////////////////////////////////
  431. // Keys
  432. func isTagKey(key []byte) bool {
  433. return strings.Count(string(key), tagKeySeparator) == 3
  434. }
  435. func extractValueFromKey(key []byte) string {
  436. parts := strings.SplitN(string(key), tagKeySeparator, 3)
  437. return parts[1]
  438. }
  439. func keyForEvent(key string, value []byte, result *types.TxResult) []byte {
  440. return []byte(fmt.Sprintf("%s/%s/%d/%d",
  441. key,
  442. value,
  443. result.Height,
  444. result.Index,
  445. ))
  446. }
  447. func keyForHeight(result *types.TxResult) []byte {
  448. return []byte(fmt.Sprintf("%s/%d/%d/%d",
  449. types.TxHeightKey,
  450. result.Height,
  451. result.Height,
  452. result.Index,
  453. ))
  454. }
  455. func startKeyForCondition(c query.Condition, height int64) []byte {
  456. if height > 0 {
  457. return startKey(c.Tag, c.Operand, height)
  458. }
  459. return startKey(c.Tag, c.Operand)
  460. }
  461. func startKey(fields ...interface{}) []byte {
  462. var b bytes.Buffer
  463. for _, f := range fields {
  464. b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator))
  465. }
  466. return b.Bytes()
  467. }