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