Merge branch 'master' of github.com:spesmilo/electrum-server
[electrum-server.git] / backends / libbitcoin / composed.py
1 import threading
2 import time
3
4 import bitcoin
5
6
7 class ExpiryQueue(threading.Thread):
8
9     def __init__(self):
10         self.lock = threading.Lock()
11         self.items = []
12         threading.Thread.__init__(self)
13         self.daemon = True
14
15     def run(self):
16         # Garbage collection
17         while True:
18             with self.lock:
19                 self.items = [i for i in self.items if not i.stopped()]
20             time.sleep(0.1)
21
22     def add(self, item):
23         with self.lock:
24             self.items.append(item)
25
26 expiry_queue = ExpiryQueue()
27
28
29 class StatementLine:
30
31     def __init__(self, output_point):
32         self.lock = threading.Lock()
33         self.output_point = output_point
34         self.output_loaded = None
35         self.input_point = None
36         self.input_loaded = None
37         self.raw_output_script = None
38
39     def is_loaded(self):
40         with self.lock:
41             if self.output_loaded is None:
42                 return False
43             elif (self.input_point is not False and
44                   self.input_loaded is None):
45                 return False
46         return True
47
48
49 class PaymentHistory:
50
51     def __init__(self, chain):
52         self.chain = chain
53         self.lock = threading.Lock()
54         self.statement = []
55         self._stopped = False
56
57     def run(self, address, handle_finish):
58         self.address = address
59         self.handle_finish = handle_finish
60
61         pubkey_hash = bitcoin.address_to_short_hash(address)
62         self.chain.fetch_outputs(pubkey_hash, self.start_loading)
63
64     def start_loading(self, ec, output_points):
65         with self.lock:
66             for outpoint in output_points:
67                 statement_line = StatementLine(outpoint)
68                 self.statement.append(statement_line)
69                 self.chain.fetch_spend(
70                     outpoint,
71                     bitcoin.bind(self.load_spend, bitcoin._1, bitcoin._2, statement_line)
72                 )
73                 self.load_tx_info(outpoint, statement_line, False)
74
75     def load_spend(self, ec, inpoint, statement_line):
76         with statement_line.lock:
77             if ec:
78                 statement_line.input_point = False
79             else:
80                 statement_line.input_point = inpoint
81         self.finish_if_done()
82         if not ec:
83             self.load_tx_info(inpoint, statement_line, True)
84
85     def finish_if_done(self):
86         with self.lock:
87             if any(not line.is_loaded() for line in self.statement):
88                 return
89         result = []
90         for line in self.statement:
91             if line.input_point:
92                 line.input_loaded["value"] = -line.output_loaded["value"]
93                 result.append(line.input_loaded)
94             else:
95                 line.output_loaded["raw_output_script"] = line.raw_output_script
96             result.append(line.output_loaded)
97         self.handle_finish(result)
98         self.stop()
99
100     def stop(self):
101         with self.lock:
102             self._stopped = True
103
104     def stopped(self):
105         with self.lock:
106             return self._stopped
107
108     def load_tx_info(self, point, statement_line, is_input):
109         info = {}
110         info["tx_hash"] = str(point.hash)
111         info["index"] = point.index
112         info["is_input"] = 1 if is_input else 0
113         self.chain.fetch_transaction_index(
114             point.hash,
115             bitcoin.bind(self.tx_index, bitcoin._1, bitcoin._2, bitcoin._3, statement_line, info)
116         )
117
118     def tx_index(self, ec, block_depth, offset, statement_line, info):
119         info["height"] = block_depth
120         self.chain.fetch_block_header_by_depth(
121             block_depth,
122             bitcoin.bind(self.block_header, bitcoin._1, bitcoin._2, statement_line, info)
123         )
124
125     def block_header(self, ec, blk_head, statement_line, info):
126         info["timestamp"] = blk_head.timestamp
127         info["block_hash"] = str(bitcoin.hash_block_header(blk_head))
128         tx_hash = bitcoin.hash_digest(info["tx_hash"])
129         self.chain.fetch_transaction(
130             tx_hash,
131             bitcoin.bind(self.load_tx, bitcoin._1, bitcoin._2, statement_line, info)
132         )
133
134     def load_tx(self, ec, tx, statement_line, info):
135         outputs = []
136         for tx_out in tx.outputs:
137             script = tx_out.output_script
138             if script.type() == bitcoin.payment_type.pubkey_hash:
139                 pkh = bitcoin.short_hash(str(script.operations()[2].data))
140                 outputs.append(bitcoin.public_key_hash_to_address(pkh))
141             else:
142                 outputs.append("Unknown")
143         info["outputs"] = outputs
144         info["inputs"] = [None for i in range(len(tx.inputs))]
145         if info["is_input"] == 1:
146             info["inputs"][info["index"]] = self.address
147         else:
148             our_output = tx.outputs[info["index"]]
149             info["value"] = our_output.value
150             with statement_line.lock:
151                 statement_line.raw_output_script = \
152                     str(bitcoin.save_script(our_output.output_script))
153         if not [empty_in for empty_in in info["inputs"] if empty_in is None]:
154             # We have the sole input
155             assert(info["is_input"] == 1)
156             with statement_line.lock:
157                 statement_line.input_loaded = info
158             self.finish_if_done()
159         for tx_idx, tx_in in enumerate(tx.inputs):
160             if info["is_input"] == 1 and info["index"] == tx_idx:
161                 continue
162             self.chain.fetch_transaction(
163                 tx_in.previous_output.hash,
164                 bitcoin.bind(self.load_input, bitcoin._1, bitcoin._2, tx_in.previous_output.index, statement_line, info, tx_idx)
165             )
166
167     def load_input(self, ec, tx, index, statement_line, info, inputs_index):
168         script = tx.outputs[index].output_script
169         if script.type() == bitcoin.payment_type.pubkey_hash:
170             pkh = bitcoin.short_hash(str(script.operations()[2].data))
171             info["inputs"][inputs_index] = \
172                 bitcoin.public_key_hash_to_address(pkh)
173         else:
174             info["inputs"][inputs_index] = "Unknown"
175         if not [empty_in for empty_in in info["inputs"] if empty_in is None]:
176             with statement_line.lock:
177                 if info["is_input"] == 1:
178                     statement_line.input_loaded = info
179                 else:
180                     statement_line.output_loaded = info
181         self.finish_if_done()
182
183
184 def payment_history(chain, address, handle_finish):
185     ph = PaymentHistory(chain)
186     expiry_queue.add(ph)
187     ph.run(address, handle_finish)
188
189
190 if __name__ == "__main__":
191     def finish(result):
192         print result
193
194     def last(ec, depth):
195         print "D:", depth
196
197     service = bitcoin.async_service(1)
198     prefix = "/home/genjix/libbitcoin/database"
199     chain = bitcoin.bdb_blockchain(service, prefix)
200     chain.fetch_last_depth(last)
201     address = "1Pbn3DLXfjqF1fFV9YPdvpvyzejZwkHhZE"
202     print "Looking up", address
203     payment_history(chain, address, finish)
204     raw_input()