2 from bitcoin import bind, _1, _2, _3
7 def __init__(self, output_point):
8 self.lock = threading.Lock()
9 self.output_point = output_point
10 self.output_loaded = None
11 self.input_point = None
12 self.input_loaded = None
13 self.raw_output_script = None
17 if self.output_loaded is None:
19 elif self.has_input() and self.input_loaded is None:
24 return self.input_point is not False
28 def __init__(self, chain):
30 self.lock = threading.Lock()
34 def start(self, address, handle_finish):
35 self.address = address
36 self.handle_finish = handle_finish
38 address = bitcoin.payment_address(address)
39 # To begin we fetch all the outputs (payments in)
40 # associated with this address
41 self.chain.fetch_outputs(address, self.start_loading)
45 assert self._stopped == False
52 def stop_on_error(self, ec):
54 self.handle_finish(None)
58 def start_loading(self, ec, output_points):
59 if self.stop_on_error(ec):
61 # Create a bunch of entry lines which are outputs and
62 # then their corresponding input (if it exists)
63 for outpoint in output_points:
64 entry = PaymentEntry(outpoint)
66 self.statement.append(entry)
67 # Attempt to fetch the spend of this output
68 self.chain.fetch_spend(outpoint,
69 bind(self.load_spend, _1, _2, entry))
70 self.load_tx_info(outpoint, entry, False)
72 def load_spend(self, ec, inpoint, entry):
73 # Need a custom self.stop_on_error(...) as a missing spend
74 # is not an error in this case.
75 if ec and ec != bitcoin.error.missing_object:
80 if ec == bitcoin.error.missing_object:
81 # This particular entry.output_point
82 # has not been spent yet
83 entry.input_point = False
85 entry.input_point = inpoint
86 if ec == bitcoin.error.missing_object:
87 # Attempt to stop if all the info for the inputs and outputs
91 # We still have to load at least one more payment outwards
92 self.load_tx_info(inpoint, entry, True)
94 def finish_if_done(self):
96 # Still have more entries to finish loading, so return
97 if any(not entry.is_loaded() for entry in self.statement):
99 # Whole operation completed successfully! Finish up.
101 for entry in self.statement:
102 if entry.input_point:
103 # value of the input is simply the inverse of
104 # the corresponding output
105 entry.input_loaded["value"] = -entry.output_loaded["value"]
106 # output should come before the input as it's chronological
107 result.append(entry.output_loaded)
108 result.append(entry.input_loaded)
110 # Unspent outputs have a raw_output_script field
111 assert entry.raw_output_script is not None
112 entry.output_loaded["raw_output_script"] = \
113 entry.raw_output_script
114 result.append(entry.output_loaded)
115 self.handle_finish(result)
118 def load_tx_info(self, point, entry, is_input):
120 info["tx_hash"] = str(point.hash)
121 info["index"] = point.index
122 info["is_input"] = 1 if is_input else 0
123 # Before loading the transaction, Stratum requires the hash
124 # of the parent block, so we load the block depth and then
125 # fetch the block header and hash it.
126 self.chain.fetch_transaction_index(point.hash,
127 bind(self.tx_index, _1, _2, _3, entry, info))
129 def tx_index(self, ec, block_depth, offset, entry, info):
130 if self.stop_on_error(ec):
132 info["height"] = block_depth
133 # And now for the block hash
134 self.chain.fetch_block_header_by_depth(block_depth,
135 bind(self.block_header, _1, _2, entry, info))
137 def block_header(self, ec, blk_head, entry, info):
138 if self.stop_on_error(ec):
140 info["timestamp"] = blk_head.timestamp
141 info["block_hash"] = str(bitcoin.hash_block_header(blk_head))
142 tx_hash = bitcoin.hash_digest(info["tx_hash"])
143 # Now load the actual main transaction for this input or output
144 self.chain.fetch_transaction(tx_hash,
145 bind(self.load_tx, _1, _2, entry, info))
147 def load_tx(self, ec, tx, entry, info):
148 if self.stop_on_error(ec):
150 # List of output addresses
152 for tx_out in tx.outputs:
153 address = bitcoin.payment_address()
154 # Attempt to extract address from output script
155 if address.extract(tx_out.output_script):
156 outputs.append(address.encoded())
158 # ... otherwise append "Unknown"
159 outputs.append("Unknown")
160 info["outputs"] = outputs
161 # For the inputs, we need the originator address which has to
162 # be looked up in the blockchain.
163 # Create list of Nones and then populate it.
164 # Loading has finished when list is no longer all None.
165 info["inputs"] = [None for i in tx.inputs]
166 # If this transaction was loaded for an input, then we already
167 # have a source address for at least one input.
168 if info["is_input"] == 1:
169 info["inputs"][info["index"]] = self.address
171 our_output = tx.outputs[info["index"]]
172 info["value"] = our_output.value
173 # Save serialised output script in case this output is unspent
175 entry.raw_output_script = \
176 str(bitcoin.save_script(our_output.output_script))
177 # If all the inputs are loaded
178 if self.inputs_all_loaded(info["inputs"]):
179 # We are the sole input
180 assert(info["is_input"] == 1)
182 entry.input_loaded = info
183 self.finish_if_done()
184 # Load the previous_output for every input so we can get
186 for input_index, tx_input in enumerate(tx.inputs):
187 if info["is_input"] == 1 and info["index"] == input_index:
189 prevout = tx_input.previous_output
190 self.chain.fetch_transaction(prevout.hash,
191 bind(self.load_input_tx, _1, _2,
192 prevout.index, entry, info, input_index))
194 def inputs_all_loaded(self, info_inputs):
195 return not [empty_in for empty_in in info_inputs if empty_in is None]
197 def load_input_tx(self, ec, tx, output_index, entry, info, input_index):
198 if self.stop_on_error(ec):
200 # For our input, we load the previous tx so we can get the
201 # corresponding output.
202 # We need the output to extract the address.
203 script = tx.outputs[output_index].output_script
204 address = bitcoin.payment_address()
205 if address.extract(script):
206 info["inputs"][input_index] = address.encoded()
208 info["inputs"][input_index] = "Unknown"
209 # If all the inputs are loaded, then we have finished loading
210 # the info for this input-output entry pair
211 if self.inputs_all_loaded(info["inputs"]):
213 if info["is_input"] == 1:
214 entry.input_loaded = info
216 entry.output_loaded = info
217 self.finish_if_done()
219 if __name__ == "__main__":
220 def blockchain_started(ec, chain):
221 print "Blockchain initialisation:", ec
225 service = bitcoin.async_service(1)
226 prefix = "/home/genjix/libbitcoin/database"
227 chain = bitcoin.bdb_blockchain(service, prefix, blockchain_started)
228 address = "1Pbn3DLXfjqF1fFV9YPdvpvyzejZwkHhZE"
229 print "Looking up", address
231 h.start(address, finish)