Merge pull request #2322 from derekpierre/get-logs

Use stateless getLogs() rpc instead of event filters across codebase
pull/2339/head
David Núñez 2020-10-06 14:57:43 +02:00 committed by GitHub
commit 32c37a6215
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 152 additions and 21 deletions

View File

@ -66,10 +66,7 @@ class ContractEvents:
if to_block is None:
to_block = 'latest'
event_filter = event_method.createFilter(fromBlock=from_block,
toBlock=to_block,
argument_filters=argument_filters)
entries = event_filter.get_all_entries()
entries = event_method.getLogs(fromBlock=from_block, toBlock=to_block, argument_filters=argument_filters)
for entry in entries:
yield EventRecord(entry)
return wrapper
@ -96,17 +93,13 @@ class ContractEventsThrottler:
to_block: int = None, # defaults to latest block
max_blocks_per_call: int = DEFAULT_MAX_BLOCKS_PER_CALL,
**argument_filters):
if not agent:
raise ValueError(f"Contract agent must be provided")
if not event_name:
raise ValueError(f"Event name must be provided")
self.event_filter = agent.events[event_name]
self.from_block = from_block
self.to_block = to_block if to_block else agent.blockchain.client.block_number
self.to_block = to_block if to_block is not None else agent.blockchain.client.block_number
# validity check of block range
if to_block <= from_block:
raise ValueError(f"Invalid block range provided ({from_block} - {to_block})")
if self.to_block < self.from_block:
raise ValueError(f"Invalid events block range: to_block {self.to_block} must be greater than or equal "
f"to from_block {self.from_block}")
self.max_blocks_per_call = max_blocks_per_call
self.argument_filters = argument_filters
@ -114,12 +107,13 @@ class ContractEventsThrottler:
def __iter__(self):
current_from_block = self.from_block
current_to_block = min(self.from_block + self.max_blocks_per_call, self.to_block)
while current_from_block < current_to_block:
while current_from_block <= current_to_block:
for event_record in self.event_filter(from_block=current_from_block,
to_block=current_to_block,
**self.argument_filters):
yield event_record
current_from_block = current_to_block
# previous block range is inclusive hence the increment
current_from_block = current_to_block + 1
# update the 'to block' to the lesser of either the next `max_blocks_per_call` blocks,
# or the remainder of blocks
current_to_block = min(current_from_block + self.max_blocks_per_call, self.to_block)

View File

@ -1149,8 +1149,7 @@ def events(general_config, staker_options, config_file, event_name):
for event in events:
emitter.echo(f"{event.event_name}:", bold=True, color='yellow')
event_filter = event.createFilter(fromBlock=0, toBlock='latest', argument_filters={'staker': staking_address})
entries = event_filter.get_all_entries()
entries = event.getLogs(fromBlock=0, toBlock='latest', argument_filters={'staker': staking_address})
for event_record in entries:
emitter.echo(f" - {EventRecord(event_record)}")

View File

@ -297,8 +297,10 @@ class EventMetricsCollector(BaseMetricsCollector):
super().__init__()
self.event_name = event_name
self.contract_agent = contract_agent
self.event_filter = contract_agent.contract.events[event_name].createFilter(fromBlock='latest',
argument_filters=argument_filters)
# this way we don't have to deal with 'latest' at all
self.filter_current_from_block = self.contract_agent.blockchain.client.block_number
self.filter_arguments = argument_filters
self.event_args_config = event_args_config
def initialize(self, metrics_prefix: str, registry: CollectorRegistry) -> None:
@ -309,9 +311,23 @@ class EventMetricsCollector(BaseMetricsCollector):
self.metrics[metric_key] = metric_class(metric_name, metric_doc, registry=registry)
def _collect_internal(self) -> None:
events = self.event_filter.get_new_entries()
for event in events:
self._event_occurred(event)
from_block = self.filter_current_from_block
to_block = self.contract_agent.blockchain.client.block_number
if from_block >= to_block:
# we've already checked the latest block and waiting for a new block
# nothing to see here
return
events_throttler = ContractEventsThrottler(agent=self.contract_agent,
event_name=self.event_name,
from_block=from_block,
to_block=to_block,
**self.filter_arguments)
for event_record in events_throttler:
self._event_occurred(event_record.raw_event)
# update last block checked for the next round - from/to block range is inclusive
self.filter_current_from_block = to_block + 1
def _event_occurred(self, event) -> None:
for arg_name in self.event_args_config:
@ -359,6 +375,10 @@ class CommitmentMadeEventMetricsCollector(EventMetricsCollector):
for event_record in events_throttler:
self._event_occurred(event_record.raw_event)
# update last block checked since we just looked for this event up to and including latest block
# block range is inclusive, hence the increment
self.filter_current_from_block = latest_block + 1
class ReStakeEventMetricsCollector(EventMetricsCollector):
"""Collector for RestakeSet event."""

View File

@ -0,0 +1,118 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from unittest.mock import Mock, MagicMock
import pytest
from nucypher.blockchain.eth.events import ContractEventsThrottler
def test_contract_events_throttler_to_block_check():
event_name = 'TestEvent'
latest_block = 50
blockchain = MagicMock()
blockchain.client.block_number = latest_block
agent = Mock(events={event_name: Mock(return_value=[])}, blockchain=blockchain)
# from_block < to_block
throttler = ContractEventsThrottler(agent=agent, event_name=event_name, from_block=1, to_block=10)
assert throttler.from_block == 1
assert throttler.to_block == 10
# to_block < from_block
with pytest.raises(ValueError):
ContractEventsThrottler(agent=agent, event_name=event_name, from_block=10, to_block=8)
# to_block can be equal to from_block
throttler = ContractEventsThrottler(agent=agent, event_name=event_name, from_block=10, to_block=10)
assert throttler.from_block == 10
assert throttler.to_block == 10
# from_block and to_block value of zero allowed
throttler = ContractEventsThrottler(agent=agent, event_name=event_name, from_block=0, to_block=0)
assert throttler.from_block == 0
assert throttler.to_block == 0
#
# when to_block is not specified it defaults to latest block number
#
# latest block is lower than from_block
with pytest.raises(ValueError):
ContractEventsThrottler(agent=agent, event_name=event_name, from_block=latest_block + 1)
# latest block is equal to from_block
throttler = ContractEventsThrottler(agent=agent, event_name=event_name, from_block=latest_block)
assert throttler.from_block == latest_block
assert throttler.to_block == latest_block
def test_contract_events_throttler_inclusive_block_ranges():
event_name = 'TestEvent'
#
# 1 block at a time
#
mock_method = Mock(return_value=[])
agent = Mock(events={event_name: mock_method})
events_throttler = ContractEventsThrottler(
agent=agent,
event_name=event_name,
from_block=0,
to_block=10,
max_blocks_per_call=1
)
for _ in events_throttler:
pass
# check calls to filter
# ranges used = (0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 10)
assert mock_method.call_count == 6
mock_method.assert_any_call(from_block=0, to_block=1)
mock_method.assert_any_call(from_block=2, to_block=3)
mock_method.assert_any_call(from_block=4, to_block=5)
mock_method.assert_any_call(from_block=6, to_block=7)
mock_method.assert_any_call(from_block=8, to_block=9)
mock_method.assert_any_call(from_block=10, to_block=10)
#
# 5 blocks at a time
#
mock_method = Mock(return_value=[])
agent = Mock(events={event_name: mock_method})
argument_filters = {'address': '0xdeadbeef'}
events_throttler = ContractEventsThrottler(
agent=agent,
event_name=event_name,
from_block=0,
to_block=21,
max_blocks_per_call=5,
**argument_filters
)
for _ in events_throttler:
pass
# check calls to filter
# ranges used = (0, 5), (6, 11), (12, 17) (18, 21)
assert mock_method.call_count == 4
mock_method.assert_any_call(**argument_filters, from_block=0, to_block=5)
mock_method.assert_any_call(**argument_filters, from_block=6, to_block=11)
mock_method.assert_any_call(**argument_filters, from_block=12, to_block=17)
mock_method.assert_any_call(**argument_filters, from_block=18, to_block=21)