-
Notifications
You must be signed in to change notification settings - Fork 418
/
Copy pathhistory.py
executable file
·163 lines (133 loc) · 5.42 KB
/
history.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env python3
import argparse
import datetime
import hashlib
import io
import sys
from logbook import Logger, StreamHandler
import prettytable
import client
log = Logger('electrum')
def _script_hash(script):
return hashlib.sha256(script).digest()[::-1].hex()
def show_rows(rows, field_names):
t = prettytable.PrettyTable()
t.field_names = field_names
t.add_rows(rows)
for f in t.field_names:
if "mBTC" in f:
t.align[f] = "r"
print(t)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost')
parser.add_argument('--network', default='mainnet')
parser.add_argument('address', nargs='+')
parser.add_argument('--only-subscribe', action='store_true', default=False)
parser.add_argument('--no-merkle-proofs', action='store_true', default=False)
args = parser.parse_args()
if args.network == 'regtest':
port = 60401
from pycoin.symbols.xrt import network
elif args.network == 'testnet':
port = 60001
from pycoin.symbols.xtn import network
elif args.network == 'mainnet':
port = 50001
from pycoin.symbols.btc import network
else:
raise ValueError(f"unknown network: {args.network}")
hostport = (args.host, port)
log.info('connecting to {}:{}', *hostport)
conn = client.Client(hostport)
tip, = conn.call([client.request('blockchain.headers.subscribe')])
script_hashes = [
_script_hash(network.parse.address(addr).script())
for addr in args.address
]
conn.call(
client.request('blockchain.scripthash.subscribe', script_hash)
for script_hash in script_hashes
)
log.info('subscribed to {} scripthashes', len(script_hashes))
if args.only_subscribe:
return
balances = conn.call(
client.request('blockchain.scripthash.get_balance', script_hash)
for script_hash in script_hashes
)
unspents = conn.call(
client.request('blockchain.scripthash.listunspent', script_hash)
for script_hash in script_hashes
)
for addr, balance, unspent in sorted(zip(args.address, balances, unspents), key=lambda v: v[0]):
if unspent:
log.debug("{}: confirmed={:,.5f} mBTC, unconfirmed={:,.5f} mBTC",
addr, balance["confirmed"] / 1e5, balance["unconfirmed"] / 1e5)
for u in unspent:
log.debug("\t{}:{} = {:,.5f} mBTC {}",
u["tx_hash"], u["tx_pos"], u["value"] / 1e5,
f'@ {u["height"]}' if u["height"] else "")
histories = conn.call(
client.request('blockchain.scripthash.get_history', script_hash)
for script_hash in script_hashes
)
txids_map = dict(
(tx['tx_hash'], tx['height'] if tx['height'] > 0 else None)
for history in histories
for tx in history
)
log.info('got history of {} transactions', len(txids_map))
txs = map(network.tx.from_hex, conn.call(
client.request('blockchain.transaction.get', txid)
for txid in txids_map.keys()
))
txs_map = dict(zip(txids_map.keys(), txs))
log.info('loaded {} transactions', len(txids_map))
confirmed_txids = {txid: height for txid, height in txids_map.items() if height is not None}
heights = set(confirmed_txids.values())
def _parse_header(header):
return network.block.parse_as_header(io.BytesIO(bytes.fromhex(header)))
headers = map(_parse_header, conn.call(
client.request('blockchain.block.header', height)
for height in heights
))
def _parse_timestamp(header):
return datetime.datetime.utcfromtimestamp(header.timestamp).strftime('%Y-%m-%dT%H:%M:%SZ')
timestamps = map(_parse_timestamp, headers)
timestamps_map = dict(zip(heights, timestamps))
log.info('loaded {} header timestamps', len(heights))
if args.no_merkle_proofs:
return
proofs = conn.call(
client.request('blockchain.transaction.get_merkle', txid, height)
for txid, height in confirmed_txids.items()
)
log.info('loaded {} merkle proofs', len(proofs)) # TODO: verify proofs
sorted_txdata = sorted(
(proof['block_height'], proof['pos'], txid)
for proof, txid in zip(proofs, confirmed_txids)
)
utxos = {}
balance = 0
rows = []
script_hashes = set(script_hashes)
for block_height, block_pos, txid in sorted_txdata:
tx_obj = txs_map[txid]
for txi in tx_obj.txs_in:
utxos.pop((str(txi.previous_hash), txi.previous_index), None)
for index, txo in enumerate(tx_obj.txs_out):
if _script_hash(txo.puzzle_script()) in script_hashes:
utxos[(txid, index)] = txo
diff = sum(txo.coin_value for txo in utxos.values()) - balance
balance += diff
confirmations = tip['height'] - block_height + 1
rows.append([txid, timestamps_map[block_height], block_height, confirmations, f'{diff/1e5:,.5f}', f'{balance/1e5:,.5f}'])
show_rows(rows, ["txid", "block timestamp", "height", "confirmations", "delta (mBTC)", "total (mBTC)"])
tip_header = _parse_header(tip['hex'])
log.info('tip={}, height={} @ {}', tip_header.id(), tip['height'], _parse_timestamp(tip_header))
unconfirmed = {txs_map[txid] for txid, height in txids_map.items() if height is None}
# TODO: show unconfirmed balance
if __name__ == '__main__':
StreamHandler(sys.stderr).push_application()
main()