Repository: tellor-io/telliot-feeds (language: python)

Issue:
Tellor RNG reporting the Same Number as a Random Number Every 15 Minutes
start command: `telliot report -a mumbai1 --rng-auto -p YOLO -ncr -wp 60 -mnb 0.1`

versions: 
`telliot-core.git@c1b98fd3371a2bfc25d594a561e4a28ff837c75a`
`telliot-feeds.git@93a86468c6b00bf089aa74c159b7025b0025500e`

Was browsing the data feed and I noticed that the random numbers all looked kind of the same, then when I decoded the values discovered that they were all exactly the same.





Discussion / hints:
update on this issue:
I updated telliot-feeds and telliot-core this morning to confirm that the bug is there. The RNG submitted a different number, but then reported that same number over again. The start command was the same.

versions:
telliot-core.git@16c5d9dcd14c6a37054e718177f3c80ca09e7654
telliot-feeds.git@d5cf93df28ec5f18094475c6633d0a596dc6119b

transactions:
https://mumbai.polygonscan.com/tx/0x9d3ee50e5547bbb58e5f9cba6245afd2f1e5484f705f2be3589f00a30eef3625
https://mumbai.polygonscan.com/tx/0x2011c670dce3e104fb29a08b872236dd57aa016ffacb24d73589242bbf0248ca

Fix patch:
diff --git a/src/telliot_feeds/cli/commands/report.py b/src/telliot_feeds/cli/commands/report.py
index e03c073d..b58086e8 100644
--- a/src/telliot_feeds/cli/commands/report.py
+++ b/src/telliot_feeds/cli/commands/report.py
@@ -369,7 +369,7 @@ async def report(
                 return
             chosen_feed = None
         elif rng_timestamp is not None:
-            chosen_feed = await assemble_rng_datafeed(timestamp=rng_timestamp, node=core.endpoint, account=account)
+            chosen_feed = await assemble_rng_datafeed(timestamp=rng_timestamp)
         else:
             chosen_feed = None
 
diff --git a/src/telliot_feeds/feeds/tellor_rng_feed.py b/src/telliot_feeds/feeds/tellor_rng_feed.py
index 66eabe50..47c002ed 100644
--- a/src/telliot_feeds/feeds/tellor_rng_feed.py
+++ b/src/telliot_feeds/feeds/tellor_rng_feed.py
@@ -1,9 +1,6 @@
 """Datafeed for pseudorandom number from hashing multiple blockhashes together."""
 from typing import Optional
 
-import chained_accounts
-from telliot_core.model.endpoints import RPCEndpoint
-
 from telliot_feeds.datafeed import DataFeed
 from telliot_feeds.queries.tellor_rng import TellorRNG
 from telliot_feeds.sources.blockhash_aggregator import TellorRNGManualSource
@@ -13,11 +10,10 @@
 tellor_rng_feed = DataFeed(source=local_source, query=TellorRNG(timestamp=local_source.timestamp))
 
 
-async def assemble_rng_datafeed(
-    timestamp: int, node: RPCEndpoint, account: chained_accounts
-) -> Optional[DataFeed[float]]:
+async def assemble_rng_datafeed(timestamp: int) -> Optional[DataFeed[float]]:
     """Assembles a TellorRNG datafeed for the given timestamp."""
-    local_source.set_timestamp(timestamp)
-    feed = DataFeed(source=local_source, query=TellorRNG(timestamp=timestamp))
+    source = TellorRNGManualSource()
+    source.set_timestamp(timestamp)
+    feed = DataFeed(source=source, query=TellorRNG(timestamp=timestamp))
 
     return feed
diff --git a/src/telliot_feeds/reporters/rng_interval.py b/src/telliot_feeds/reporters/rng_interval.py
index f6cb9ead..27ab8e6d 100644
--- a/src/telliot_feeds/reporters/rng_interval.py
+++ b/src/telliot_feeds/reporters/rng_interval.py
@@ -53,7 +53,7 @@ async def fetch_datafeed(self) -> Optional[DataFeed[Any]]:
             logger.info(status.error)
             return None
 
-        datafeed = await assemble_rng_datafeed(timestamp=rng_timestamp, node=self.endpoint, account=self.account)
+        datafeed = await assemble_rng_datafeed(timestamp=rng_timestamp)
         if datafeed is None:
             msg = "Unable to assemble RNG datafeed"
             error_status(note=msg, log=logger.warning)


Test patch:
diff --git a/tests/reporters/test_rng_reporter.py b/tests/reporters/test_rng_reporter.py
index 8c4a1412..ce45a93d 100644
--- a/tests/reporters/test_rng_reporter.py
+++ b/tests/reporters/test_rng_reporter.py
@@ -7,6 +7,8 @@
 from telliot_core.utils.response import ResponseStatus
 from web3.datastructures import AttributeDict
 
+from telliot_feeds.datafeed import DataFeed
+from telliot_feeds.queries.tellor_rng import TellorRNG
 from telliot_feeds.reporters.rng_interval import logger
 from telliot_feeds.reporters.rng_interval import RNGReporter
 
@@ -17,6 +19,10 @@ def mock_zero_timestamp():
     return 0
 
 
+async def mock_response_status(*args, **kwargs):
+    return ResponseStatus()
+
+
 @pytest_asyncio.fixture(scope="function")
 async def rng_reporter(mumbai_test_cfg, tellor_360):
     async with TelliotCore(config=mumbai_test_cfg) as core:
@@ -97,15 +103,9 @@ async def mock_online(*args, **kwargs):
 
     r.is_online = mock_online
 
-    async def mock_check_reporter_lock(*args, **kwargs):
-        return ResponseStatus()
-
-    r.check_reporter_lock = mock_check_reporter_lock
-
-    async def mock_ensure_profitable(*args, **kwargs):
-        return ResponseStatus()
+    r.check_reporter_lock = mock_response_status
 
-    r.ensure_profitable = mock_ensure_profitable
+    r.ensure_profitable = mock_response_status
 
     await r.report(report_count=3)
 
@@ -145,3 +145,63 @@ async def test_invalid_timestamp_in_future(rng_reporter, monkeypatch, caplog):
         assert status.ok
         assert receipt["status"] == 1
         assert "less than current time" in caplog.text
+
+
+def generate_numbers():
+    current_number = 1678311000
+    while current_number > 1678310000:
+        yield current_number
+        current_number = int(time.time())
+
+
+@pytest.mark.asyncio
+async def test_repeat_auto_rng_report_value(rng_reporter: RNGReporter, monkeypatch, caplog):
+    """Test that shows the bad behavior before bug fix that was causing same value
+    to be reported for timestamps that didn't have block hashes generated yet.
+    Reason is global scope of local_source variable in assemble_rng_datafeed function
+    """
+    # function before fix was applied used local_source var from global scope
+    from telliot_feeds.feeds.tellor_rng_feed import local_source
+
+    async def assemble_feed(timestamp):
+        local_source.set_timestamp(timestamp)
+        return DataFeed(source=local_source, query=TellorRNG(timestamp=timestamp))
+
+    rng_reporter.wait_period = 0
+    num = generate_numbers()
+    # test to show difference before fix
+    with monkeypatch.context() as m:
+        m.setattr("telliot_feeds.reporters.rng_interval.assemble_rng_datafeed", assemble_feed)
+        m.setattr("telliot_feeds.reporters.rng_interval.get_next_timestamp", lambda: next(num))
+        m.setattr(
+            "telliot_feeds.reporters.rng_interval.RNGReporter.check_reporter_lock",
+            mock_response_status,
+        )
+        # first report uses timestamp that has block hashes avaialble to generate rng
+        # second timestamp does not have block hashes yet because it is current
+        await rng_reporter.report(2)
+        # encoded value for timestamp 1678311000
+        count = caplog.text.count(
+            "IntervalReporter Encoded value: 236eabcc1c1dc5c01bd6357576b17e490eaf7aaa37b360485cddcd6877a395c3"
+        )
+        # count is 2 because second report uses same value
+        assert count == 2
+
+
+@pytest.mark.asyncio
+async def test_unique_rng_report_value(rng_reporter: RNGReporter, monkeypatch, caplog):
+    """Test that rng report value isn't submitting the same value twice."""
+    rng_reporter.wait_period = 0
+    num = generate_numbers()
+
+    with monkeypatch.context() as m:
+        m.setattr("telliot_feeds.reporters.rng_interval.get_next_timestamp", lambda: next(num))
+        m.setattr(
+            "telliot_feeds.reporters.rng_interval.RNGReporter.check_reporter_lock",
+            mock_response_status,
+        )
+        await rng_reporter.report(2)
+        count = caplog.text.count(
+            "IntervalReporter Encoded value: 236eabcc1c1dc5c01bd6357576b17e490eaf7aaa37b360485cddcd6877a395c3"
+        )
+        assert count == 1