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:
parent
6cbc2a94ee
commit
4726d3259e
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user