forked from trezor/trezor-firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tx_cache.py
executable file
·135 lines (107 loc) · 4.27 KB
/
tx_cache.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env python3
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2019 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library 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 Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json
import sys
from decimal import Decimal
from pathlib import Path
import click
import requests
from trezorlib import btc, messages, protobuf
REPOSITORY_ROOT = Path(__file__).resolve().parent.parent
TOOLS_PATH = REPOSITORY_ROOT / "common" / "tools"
CACHE_PATH = Path(__file__).resolve().parent / "txcache"
sys.path.insert(0, str(TOOLS_PATH))
from coin_info import coin_info # isort:skip
def _get_blockbooks():
"""Make a list of blockbook URL patterns available.
Only used to prefill the BLOCKBOOKS variable.
"""
coins = coin_info().bitcoin
res = {}
for coin in coins:
if not coin.get("blockbook"):
continue
res[coin["coin_name"].lower()] = coin["blockbook"][0] + "/api/tx-specific/{}"
return res
BLOCKBOOKS = _get_blockbooks()
class TxCache:
def __init__(self, coin_name: str) -> None:
self.slug = coin_name.lower().replace(" ", "_")
def get_tx(self, txhash: str) -> messages.TransactionType:
try:
(CACHE_PATH / self.slug).mkdir()
except Exception:
pass
cache_file = CACHE_PATH / self.slug / f"{txhash}.json"
if not cache_file.exists():
raise RuntimeError(
f"cache miss for {self.slug} tx {txhash}.\n"
"To fix, refer to ./tests/tx_cache.py --help"
)
txdict = json.loads(cache_file.read_text())
return protobuf.dict_to_proto(messages.TransactionType, txdict)
def __getitem__(self, key: bytes) -> messages.TransactionType:
return self.get_tx(key.hex())
def __contains__(self, key: bytes) -> bool:
try:
self.get_tx(key.hex())
return True
except Exception:
return False
@click.command()
@click.argument("coin_name")
@click.argument("tx", metavar="TXHASH_OR_URL")
def cli(tx, coin_name):
"""Add a transaction to the cache.
\b
Without URL, default blockbook server will be used:
./tests/tx_cache.py bcash bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78
It is also possible to specify URL explicitly:
./tests/tx_cache.py bcash https://bch1.trezor.io/api/tx-specific/bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78
The transaction will be parsed into Trezor format and saved in
tests/txcache/<COIN_NAME>/<TXHASH>.json. Note that only Bitcoin-compatible fields
will be filled out. If you are adding a coin with special fields (Dash, Zcash...),
it is your responsibility to fill out the missing fields properly.
"""
if tx.startswith("http"):
tx_url = tx
tx_hash = tx.split("/")[-1].lower()
elif coin_name not in BLOCKBOOKS:
raise click.ClickException(
f"Could not find blockbook for {coin_name}. Please specify a full URL."
)
else:
tx_hash = tx.lower()
tx_url = BLOCKBOOKS[coin_name].format(tx)
click.echo(f"Fetching from {tx_url}...")
try:
# Get transaction from Blockbook server. The servers refuse requests with an empty user agent.
tx_src = requests.get(tx_url, headers={"user-agent": "tx_cache"}).json(
parse_float=Decimal
)
tx_proto = btc.from_json(tx_src)
tx_dict = protobuf.to_dict(tx_proto)
tx_json = json.dumps(tx_dict, sort_keys=True, indent=2) + "\n"
except Exception as e:
raise click.ClickException(str(e)) from e
cache_dir = CACHE_PATH / coin_name
if not cache_dir.exists():
cache_dir.mkdir()
(cache_dir / f"{tx_hash}.json").write_text(tx_json)
click.echo(tx_json)
if __name__ == "__main__":
cli()