-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblockchain_watcher.py
128 lines (109 loc) · 4.05 KB
/
blockchain_watcher.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
import traceback
from decimal import Decimal
from time import time
from electroncash.util import PrintError
from electroncash.network import Network
from electroncash.address import Address
from electroncash.transaction import Transaction
from .model import TipListener
class BlockchainWatcher(TipListener, PrintError):
"""
BlockchainWatcher
listens for tips
and uses electrum network to watch for payments to recipient addresses
in order to mark tips as paid
"""
def __init__(self, wallet, tiplist):
self.wallet = wallet
self.network = self.wallet.weak_window().network
self.tiplist = tiplist
self.tiplist.registerTipListener(self)
self.hash2tip = {}
self.requested_tx_hashes = {}
self.tips_by_address = {} # track tips by address
self.tipless_payments_by_address = {} # track payments that did not have a tip associated at the time of receiving
def __del__(self):
self.tiplist.unregisterTipListener(self)
def debug_stats(self):
return f" BlockchainWatcher: {len(self.hash2tip.keys())} scripthash subscriptions"
# stolen from synchronizer
def parse_response(self, response):
error = True
try:
if not response: return None, None, error
error = response.get('error')
return response['params'], response.get('result'), error
finally:
if error:
self.print_error("response error:", response)
# TipListener overrides
def tipRemoved(self, tip):
self.tips_by_address.pop(tip.recipient_address, None)
def tipAdded(self, tip):
self.tipUpdated(tip)
def tipUpdated(self, tip):
if isinstance(tip.recipient_address, Address):
# check if already seen a payment
if tip.recipient_address in self.tipless_payments_by_address:
payment = self.tipless_payments_by_address[tip.recipient_address]
tip.registerPayment(payment["tx_hash"], payment["amount_bch"], "chain")
# subscribe to recipient address scripthash
if tip.recipient_address not in self.tips_by_address:
self.tips_by_address[tip.recipient_address] = tip
scripthash = tip.recipient_address.to_scripthash_hex()
if scripthash not in self.hash2tip.keys():
self.hash2tip[scripthash] = tip
# subscribe to scripthash
if not self.network:
self.print_error("no network, unable to check for tip payments")
else:
#self.print_error("subscribing to ", tip.recipient_address)
self.network.subscribe_to_scripthashes([scripthash], self.on_status_change)
tip.subscription_time = time()
def on_status_change(self, c):
scripthash = c["params"][0]
#txhash = c["result"]
tip = self.hash2tip[scripthash]
# get scripthash history
self.network.request_scripthash_history(scripthash, self.on_address_history)
def on_address_history(self, response):
params, result, error = self.parse_response(response)
if error:
return
tx_hashes = map(lambda item: item['tx_hash'], result)
self.request_tx(tx_hashes)
def request_tx(self, tx_hashes):
requests = []
for tx_hash in tx_hashes:
if tx_hash not in self.requested_tx_hashes.keys():
self.requested_tx_hashes[tx_hash] = 17
requests.append(('blockchain.transaction.get', [tx_hash]))
#self.print_error("requesting transactions, requests: ", requests)
self.network.send(requests, self.on_tx)
def on_tx(self, response):
#self.print_error("--- got tx response: ", response)
params, result, error = self.parse_response(response)
tx_hash = params[0] or ''
if error:
self.print_error("error for tx_hash {}, skipping".format(tx_hash))
return
try:
tx = Transaction(result)
for o in tx.outputs():
#self.print_error(" output", o)
address = o[1]
satoshis = o[2]
tip = self.tips_by_address.get(address, None)
if tip:
tip.registerPayment(tx_hash, Decimal("0.00000001") * satoshis, "chain")
else:
self.tipless_payments_by_address[address] = {
"tx_hash": tx_hash,
"amount_bch": Decimal("0.00000001") * satoshis
}
# else:
# self.print_error("address", address, ": cannot find associated tip")
except Exception:
traceback.print_exc()
self.print_msg("cannot deserialize transaction, skipping", tx_hash)
return