1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-15 09:50:57 +00:00

tools: update coin_info to new support method and duplicate checking

This commit is contained in:
matejcik 2018-08-15 19:22:29 +02:00
parent 6cbc2a94ee
commit 4726d3259e

View File

@ -185,25 +185,6 @@ def validate_btc(coin):
return errors return errors
SUPPORT_CHECKS = [
check_key("trezor1", str, nullable=True, regex=r"^soon|planned|\d+\.\d+\.\d+$"),
check_key("trezor2", str, nullable=True, regex=r"^soon|planned|\d+\.\d+\.\d+$"),
check_key("webwallet", bool, nullable=True),
check_key("connect", bool, nullable=True),
check_key("other", dict, optional=True, empty=False),
]
def validate_support(support):
errors = []
for check in SUPPORT_CHECKS:
try:
check(support)
except Exception as e:
errors.append(str(e))
return errors
# ======= Coin json loaders ======= # ======= Coin json loaders =======
@ -280,15 +261,6 @@ def _load_misc():
# ====== support info ====== # ====== support info ======
RELEASES_URL = "https://wallet.trezor.io/data/firmware/{}/releases.json" RELEASES_URL = "https://wallet.trezor.io/data/firmware/{}/releases.json"
ETHEREUM_TOKENS = {
"1": "https://raw.githubusercontent.com/trezor/trezor-mcu/v{}/firmware/ethereum_tokens.c",
"2": "https://raw.githubusercontent.com/trezor/trezor-core/v{}/src/apps/ethereum/tokens.py",
}
TOKEN_MATCH = {
"1": re.compile(r'\{.*" ([^"]+)".*\},'),
"2": re.compile(r'\(.*["\']([^"\']+)["\'].*\),'),
}
def get_support_data(): def get_support_data():
@ -308,77 +280,46 @@ def latest_releases():
return latest return latest
def support_info_erc20(coins, versions): def support_info_single(support_data, coin):
"""Generate support info for ERC20 tokens. """Extract a support dict from `support.json` data.
Takes a dict of Trezor versions as returned from `latest_releases` Returns a dict of support values for each "device", i.e., `support.json`
and a list of coins as returned from `_get_erc20_tokens` and creates top-level key.
a supportinfo entry for each listed token.
Support status is determined by downloading and parsing the definition file The support value for each device is determined in order of priority:
from the appropriate firmware version. If a given token is listed, the support * if the coin is marked as duplicate, all support values are `None`
is marked as "yes". If not, support is marked as "soon", assuming that * if the coin has an entry in `unsupported`, its support is `None`
it will be included in next release. * if the coin has an entry in `supported` its support is that entry
(usually a version string, or `True` for connect/webwallet)
This is currently the only way to get the list of supported tokens. We don't want * otherwise support is presumed "soon"
to track support individually in support.json.
""" """
supported_tokens = {} support_info = {}
for trezor, version in versions.items(): key = coin["key"]
tokens = set() dup = coin.get("duplicate")
version_str = ".".join(map(str, version)) for device, values in support_data.items():
if dup:
token_file = requests.get(ETHEREUM_TOKENS[trezor].format(version_str)).text support_value = None
token_match = TOKEN_MATCH[trezor] elif key in values["unsupported"]:
support_value = None
for line in token_file.split("\n"): elif key in values["supported"]:
m = token_match.search(line) support_value = values["supported"][key]
if m: else:
tokens.add(m.group(1)) support_value = "soon"
support_info[device] = support_value
supported_tokens[trezor] = tokens return support_info
support = {}
for coin in coins:
key = coin["key"]
if not key.startswith("erc20:"):
continue
support[key] = dict(
trezor1="yes" if coin["shortcut"] in supported_tokens["1"] else "soon",
trezor2="yes" if coin["shortcut"] in supported_tokens["2"] else "soon",
)
return support
def support_info(coins, erc20_versions=None, skip_missing=False): def support_info(coins):
"""Generate Trezor support information. """Generate Trezor support information.
Takes a collection of coins and generates a support-info entry for each. Takes a collection of coins and generates a support-info entry for each.
The support-info is a dict with a number of known keys: The support-info is a dict with keys based on `support.json` keys.
`trezor1`, `trezor2`, `webwallet`, `connect`. An optional `other` entry These are usually: "trezor1", "trezor2", "connect" and "webwallet".
is a dict of name-url pairs for third-party software.
The `coins` argument can be a `CoinsInfo` object, a list or a dict of The `coins` argument can be a `CoinsInfo` object, a list or a dict of
coin items. coin items.
For btc-like coins and misc networks, this is taken from `support.json`. Support information is taken from `support.json`.
For NEM mosaics and ethereum networks, the support is presumed to be "yes"
for both Trezors. Webwallet and Connect info is not filled out.
ERC20 tokens are ignored by this function, as if `skip_missing` was true
(see below). However, if you pass the optional `erc20_versions` argument,
it will call `support_info_erc20` for you with given versions.
In all cases, if the coin is explicitly listed in `support.json`, the info
takes precedence over any other source (be it assumed "yes" for nem/eth,
or downloaded info for erc20).
If `skip_missing` is `True`, coins for which no support information is available
will not be included in the output. Otherwise, an empty dict will be included
and a warning emitted. "No support information" means that the coin is not
listed in `support.json` and we have no heuristic to determine the support.
""" """
if isinstance(coins, CoinsInfo): if isinstance(coins, CoinsInfo):
coins = coins.as_list() coins = coins.as_list()
@ -388,28 +329,9 @@ def support_info(coins, erc20_versions=None, skip_missing=False):
support_data = get_support_data() support_data = get_support_data()
support = {} support = {}
for coin in coins: for coin in coins:
key = coin["key"] support[coin["key"]] = support_info_single(support_data, coin)
typ = key.split(":", maxsplit=1)[0]
if key in support_data:
support[key] = support_data[key]
elif typ in ("nem", "eth"): return support
support[key] = dict(trezor1="yes", trezor2="yes")
elif typ == "erc20":
# you must call a separate function to get that
continue
elif not skip_missing:
log.info("support info missing for {}".format(key))
support[key] = {}
if erc20_versions:
erc20 = support_info_erc20(coins, erc20_versions)
erc20.update(support)
return erc20
else:
return support
# ====== data cleanup functions ====== # ====== data cleanup functions ======
@ -459,31 +381,38 @@ def _ensure_mandatory_values(coins):
raise ValueError(coin) raise ValueError(coin)
def _filter_duplicate_shortcuts(coins): def mark_duplicate_shortcuts(coins):
"""Removes coins with identical `shortcut`s. """Finds coins with identical `shortcut`s.
This is used to drop colliding ERC20 tokens. Updates their keys and sets a `duplicate` field.
""" """
dup_keys = set() dup_symbols = defaultdict(list)
retained_coins = OrderedDict() dup_keys = defaultdict(list)
def dups_only(dups):
return {k: v for k, v in dups.items() if len(v) > 1}
for coin in coins: for coin in coins:
if "Testnet" in coin["name"] and coin["shortcut"] == "tETH": symsplit = coin["shortcut"].split(" ", maxsplit=1)
# special case for Ethereum testnets symbol = symsplit[0]
continue dup_symbols[symbol].append(coin)
dup_keys[coin["key"]].append(coin)
key = coin["shortcut"] dup_symbols = dups_only(dup_symbols)
if key in dup_keys: dup_keys = dups_only(dup_keys)
pass
elif key in retained_coins:
dup_keys.add(key)
del retained_coins[key]
else:
retained_coins[key] = coin
# modify original list # mark duplicate symbols
coins[:] = retained_coins.values() for values in dup_symbols.values():
# return duplicates for coin in values:
return dup_keys coin["duplicate"] = True
# deduplicate keys
for values in dup_keys.values():
for i, coin in enumerate(values):
# presumably only duplicate symbols can have duplicate keys
assert coin.get("duplicate")
coin["key"] += f":{i}"
return dup_symbols
def _btc_sort_key(coin): def _btc_sort_key(coin):
@ -493,13 +422,15 @@ def _btc_sort_key(coin):
return coin["name"] return coin["name"]
def get_all(): def get_all(deduplicate=True):
"""Returns all definition as dict organized by coin type. """Returns all definition as dict organized by coin type.
`coins` for btc-like coins, `coins` for btc-like coins,
`eth` for ethereum networks, `eth` for ethereum networks,
`erc20` for ERC20 tokens, `erc20` for ERC20 tokens,
`nem` for NEM mosaics, `nem` for NEM mosaics,
`misc` for other networks. `misc` for other networks.
Automatically removes duplicate symbols from the result.
""" """
all_coins = CoinsInfo( all_coins = CoinsInfo(
coins=_load_btc_coins(), coins=_load_btc_coins(),
@ -515,17 +446,18 @@ def get_all():
elif k == "nem": elif k == "nem":
# do not sort nem # do not sort nem
pass pass
elif k == "eth":
# sort ethereum networks by chain_id
coins.sort(key=lambda c: c["chain_id"])
else: else:
coins.sort(key=lambda c: c["key"].upper()) coins.sort(key=lambda c: c["key"].upper())
_ensure_mandatory_values(coins) _ensure_mandatory_values(coins)
dup_keys = _filter_duplicate_shortcuts(coins)
if dup_keys: if deduplicate:
if k == "erc20": mark_duplicate_shortcuts(all_coins.as_list())
severity = logging.INFO all_coins["erc20"] = [
else: coin for coin in all_coins["erc20"] if not coin.get("duplicate")
severity = logging.WARNING ]
dup_str = ", ".join(dup_keys)
log.log(severity, "{}: removing duplicate symbols: {}".format(k, dup_str))
return all_coins return all_coins