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

Chainslist and yesflag #185

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,26 @@ source vars.sh

### Set the chains you want to monitor

- Edit `monitored-chains.json` with the desired set of networks that you wish to monitor with the disputanble values monitor.

example, if you would like to monitor values only on mainnet Ethereum, sepolia, Amoy and localhost:
```
{
"monitored_chains": [1, 11155111, 80002, 1337]
}
```
*Note: Ethereum, sepolia, Amoy and localhost are required for testing.*

### Set RPC Endpoints
- Initialize telliot configuration:
```bash
poetry run telliot config init
```
You should now have a `telliot` folder in your home directory.

- Open `~/telliot/endpoints.yaml` with your favorite text editor. The Disputable-Values-Monitor will check reports on each chain that is configured in this list. Remove the networks that you don't want to monitor, and provide and endpoint url for those that you do. For example, if you want to monitor reports on Ethereum Mainnet and Sepolia testnet, your endpoints.yaml file should look like this:
- Open `~/telliot/endpoints.yaml` with your favorite text editor. Provide a reliable endpoint url for each chain that you would like to monitor. If any chain endpoints are needed by telliot-feeds for the data that you wish to monitor, add those to this file as well.

Example continued: You want to monitor reports on Sepolia (11155111) and Amoy (80002), but you want to make sure that any incorrect wsteth-usd-spot reports get flagged. Because the `wsteth-usd-spot` telliot feed requireds a mainnet (1) endpoint, you need to have a working endpoint for sepolia, Amoy, and mainnet:
```
type: EndpointList
endpoints:
Expand All @@ -74,6 +87,13 @@ endpoints:
provider: Infura
url: https://YOUR_SEPOLIA_ENDPOINT
explorer: https://sepolia.etherscan.io/
- type: RPCEndpoint
chain_id: 80002
network: polygon-amoy
provider: Matic
url: https://YOUR_AMOY_ENDPOINT
explorer: https://amoy.polygonscan.com/
monitored: True
```

### Run the DVM for Alerts Only
Expand All @@ -92,6 +112,10 @@ Enter `y` to confirm alerts only.

`-av`: to get an alert for all `NewReport` events (regardless of whether they are disputable or not).

`--initial_block_offset` : The number of blocks to look back when first starting the DVM. (CAUTION: stale data can cause false positives if this value is set too large, or if prices moved drastically in the recent past)

`-sc` : Use "skip confirmations" when running the DVM as a system service to skip config checks.

### Run the DVM for Automatic Disputes

**Disclaimer:**
Expand Down
3 changes: 3 additions & 0 deletions monitored-chains.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"monitored_chains": [1, 11155111, 80002, 1337]
}
21 changes: 16 additions & 5 deletions src/disputable_values_monitor/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@

def print_title_info() -> None:
"""Prints the title info."""
click.echo("Disputable Values Monitor 📒🔎📲")
click.echo("Disputable Values Monitor Starting... 📒🔎📲")


@click.command()
@click.option(
"-av", "--all-values", is_flag=True, default=False, show_default=True, help="if set, get alerts for all values"
)
@click.option("-a", "--account-name", help="the name of a ChainedAccount to dispute with", type=str)
@click.option("-pwd", "--password", help="password for your account (req'd if -sc is used)", type=str)
@click.option("-w", "--wait", help="how long to wait between checks", type=int, default=WAIT_PERIOD)
@click.option("-d", "--is-disputing", help="enable auto-disputing on chain", is_flag=True)
@click.option(
Expand All @@ -61,6 +62,7 @@ def print_title_info() -> None:
type=int,
default=0,
)
@click.option("-sc", "skip_confirmations", help="skip confirm configuration (unsafe start)", is_flag=True)
@async_run
async def main(
all_values: bool,
Expand All @@ -69,6 +71,8 @@ async def main(
is_disputing: bool,
confidence_threshold: float,
initial_block_offset: int,
skip_confirmations: bool,
password: str,
) -> None:
"""CLI dashboard to display recent values reported to Tellor oracles."""
# Raises exception if no webhook url is found
Expand All @@ -80,6 +84,8 @@ async def main(
is_disputing=is_disputing,
confidence_threshold=confidence_threshold,
initial_block_offset=initial_block_offset,
skip_confirmations=skip_confirmations,
password=password,
)


Expand All @@ -90,6 +96,8 @@ async def start(
is_disputing: bool,
confidence_threshold: float,
initial_block_offset: int,
skip_confirmations: bool,
password: str,
) -> None:
"""Start the CLI dashboard."""
cfg = TelliotConfig()
Expand All @@ -98,13 +106,16 @@ async def start(
print_title_info()

if not disp_cfg.monitored_feeds:
logger.error("No feeds set for monitoring, please add feeds to ./disputer-config.yaml")
click.echo("No feeds set for monitoring, please add feeds to ./disputer-config.yaml")
logger.error("No feeds set for monitoring, please check ./disputer-config.yaml")
return

account: ChainedAccount = select_account(cfg, account_name)
if not account_name and is_disputing:
click.echo("An account is required for auto-disputing (see --help)")
logger.error("auto-disputing enabled, but no account provided (see --help)")
return

if account and is_disputing:
click.echo("...Now with auto-disputing!")
account: ChainedAccount = select_account(cfg, account_name, password, skip_confirmations)

display_rows = []
displayed_events = set()
Expand Down
15 changes: 14 additions & 1 deletion src/disputable_values_monitor/data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Get and parse NewReport events from Tellor oracles."""
import asyncio
import json
import math
from dataclasses import dataclass
from enum import Enum
Expand Down Expand Up @@ -320,14 +321,18 @@ async def chain_events(
async def get_events(
cfg: TelliotConfig, contract_name: str, topics: list[str], inital_block_offset: int
) -> List[List[tuple[int, Any]]]:
"""Get all events from all live Tellor networks"""
"""Get all events from monitored chains listed in chains.json"""

monitored_list = get_monitored_chains_json()

log_loops = []

for endpoint in cfg.endpoints.endpoints:
if endpoint.url.endswith("{INFURA_API_KEY}"):
continue
chain_id = endpoint.chain_id
if chain_id not in monitored_list:
continue
try:
endpoint.connect()
except Exception as e:
Expand Down Expand Up @@ -579,4 +584,12 @@ def get_block_number_at_timestamp(cfg: TelliotConfig, timestamp: int) -> Any:


def get_feed_from_catalog(tag: str) -> Optional[DataFeed]:
"""gets a list of chains to monitor from monitored-chains.json"""
return CATALOG_FEEDS.get(tag)


def get_monitored_chains_json() -> Any:
"""gets a list of chains to monitor"""
with open("monitored-chains.json", "r") as f:
config = json.load(f)
return config.get("monitored_chains", [])
37 changes: 22 additions & 15 deletions src/disputable_values_monitor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,31 @@ def clear_console() -> None:
_ = os.system("clear")


def select_account(cfg: TelliotConfig, account: Optional[str]) -> Optional[ChainedAccount]:
def select_account(
cfg: TelliotConfig, account: Optional[str], password: Optional[str], skip_confirmations: Optional[bool]
) -> Optional[ChainedAccount]:
"""Select an account for disputing, allow no account to be chosen."""

if account is not None:
accounts = find_accounts(name=account)
click.echo(f"Your account name: {accounts[0].name if accounts else None}")
else:
run_alerts_only = click.confirm("Missing an account to send disputes. Run alerts only?")
if not run_alerts_only:
new_account = setup_account(cfg.main.chain_id)
if new_account is not None:
click.echo(f"{new_account.name} selected!")
return new_account
accounts = None
if account is None:
if skip_confirmations:
return None
else:
return None

accounts[0].unlock()
run_alerts_only = click.confirm("Missing an account to send disputes. Run alerts only?")
if not run_alerts_only:
new_account = setup_account(cfg.main.chain_id)
if new_account is not None:
click.echo(f"{new_account.name} selected!")
return new_account
return None
else:
return None
else:
accounts = find_accounts(name=account)
if password is None:
accounts[0].unlock()
click.echo(f"Your account name: {accounts[0].name if accounts else None}")
else:
accounts[0].unlock(password=password)
return accounts[0]


Expand Down
15 changes: 9 additions & 6 deletions tests/test_auto_dispute_multiple_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,12 @@ async def submit_multiple_bad_values(stake_deposited: Awaitable[TelliotCore]):
@pytest.mark.asyncio
async def fetch_timestamp(oracle, query_id, chain_timestamp):
"""fetches a value's timestamp from oracle"""
timestamp, status = await oracle.read("getDataBefore", query_id, chain_timestamp)
assert timestamp[2] > 0
assert status.ok, status.error
try:
timestamp, status = await oracle.read("getDataBefore", query_id, chain_timestamp)
assert timestamp is not None and len(timestamp) > 2 and timestamp[2] > 0
assert status.ok, status.error
except Exception as e:
pytest.fail(f"Failed to fetch a valid timestamp: {e}")
return timestamp


Expand All @@ -165,7 +168,7 @@ async def check_dispute(oracle, query_id, timestamp):
return indispute


async def setup_and_start(is_disputing, config, config_patches=None):
async def setup_and_start(is_disputing, config, config_patches=None, skip_confirmations=False, password=None):
# using exit stack makes nested patching easier to read
with ExitStack() as stack:
stack.enter_context(patch("getpass.getpass", return_value=""))
Expand All @@ -187,7 +190,7 @@ async def setup_and_start(is_disputing, config, config_patches=None):

try:
async with async_timeout.timeout(9):
await start(False, 8, "disputer-test-acct", is_disputing, 0.1, 0)
await start(False, 8, "disputer-test-acct", is_disputing, 0.1, 0, skip_confirmations, password)
except asyncio.TimeoutError:
pass

Expand Down Expand Up @@ -300,7 +303,7 @@ async def test_evm_type_alert(submit_multiple_bad_values: Awaitable[TelliotCore]
]
# not disputing just alerting
# if evm type is in dispute config it will be checked for equality
await setup_and_start(False, config, config_patches)
await setup_and_start(False, config, config_patches, skip_confirmations=True, password=None)
assert "Cannot evaluate percent difference on text/addresses/bytes" in caplog.text


Expand Down
4 changes: 2 additions & 2 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ def test_select_account():
cfg = TelliotConfig()

if not find_accounts("disputer-test-acct"):
ChainedAccount.add("disputer-test-acct1", [1, 5, 4, 1337, 80001], os.getenv("PRIVATE_KEY"), "")
ChainedAccount.add("disputer-test-acct", [1, 5, 4, 1337, 80001, 80002, 11155111], os.getenv("PRIVATE_KEY"), "")

with mock.patch("click.confirm", return_value=True):
account = select_account(cfg, None)
account = select_account(cfg, None, False, None)

assert not account

Expand Down
Loading