2 from bitcoin import _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, service, chain):
29 self.wrap = bitcoin.Strand(service).wrap
31 self.lock = threading.Lock()
35 def start(self, address, handle_finish):
36 self.address = address
37 self.handle_finish = handle_finish
39 address = bitcoin.payment_address(address)
40 # To begin we fetch all the outputs (payments in)
41 # associated with this address
42 self.chain.fetch_outputs(address,
43 self.wrap(self.start_loading, _1, _2))
47 assert self._stopped == False
54 def stop_on_error(self, ec):
56 self.handle_finish(None)
60 def start_loading(self, ec, output_points):
61 if self.stop_on_error(ec):
63 # Create a bunch of entry lines which are outputs and
64 # then their corresponding input (if it exists)
65 for outpoint in output_points:
66 entry = PaymentEntry(outpoint)
68 self.statement.append(entry)
69 # Attempt to fetch the spend of this output
70 self.chain.fetch_spend(outpoint,
71 self.wrap(self.load_spend, _1, _2, entry))
72 self.load_tx_info(outpoint, entry, False)
74 def load_spend(self, ec, inpoint, entry):
75 # Need a custom self.stop_on_error(...) as a missing spend
76 # is not an error in this case.
77 if ec and ec != bitcoin.error.missing_object:
82 if ec == bitcoin.error.missing_object:
83 # This particular entry.output_point
84 # has not been spent yet
85 entry.input_point = False
87 entry.input_point = inpoint
88 if ec == bitcoin.error.missing_object:
89 # Attempt to stop if all the info for the inputs and outputs
93 # We still have to load at least one more payment outwards
94 self.load_tx_info(inpoint, entry, True)
96 def finish_if_done(self):
98 # Still have more entries to finish loading, so return
99 if any(not entry.is_loaded() for entry in self.statement):
101 # Whole operation completed successfully! Finish up.
103 for entry in self.statement:
104 if entry.input_point:
105 # value of the input is simply the inverse of
106 # the corresponding output
107 entry.input_loaded["value"] = -entry.output_loaded["value"]
108 # output should come before the input as it's chronological
109 result.append(entry.output_loaded)
110 result.append(entry.input_loaded)
112 # Unspent outputs have a raw_output_script field
113 assert entry.raw_output_script is not None
114 entry.output_loaded["raw_output_script"] = \
115 entry.raw_output_script
116 result.append(entry.output_loaded)
117 self.handle_finish(result)
120 def load_tx_info(self, point, entry, is_input):
122 info["tx_hash"] = str(point.hash)
123 info["index"] = point.index
124 info["is_input"] = 1 if is_input else 0
125 # Before loading the transaction, Stratum requires the hash
126 # of the parent block, so we load the block depth and then
127 # fetch the block header and hash it.
128 self.chain.fetch_transaction_index(point.hash,
129 self.wrap(self.tx_index, _1, _2, _3, entry, info))
131 def tx_index(self, ec, block_depth, offset, entry, info):
132 if self.stop_on_error(ec):
134 info["height"] = block_depth
135 # And now for the block hash
136 self.chain.fetch_block_header_by_depth(block_depth,
137 self.wrap(self.block_header, _1, _2, entry, info))
139 def block_header(self, ec, blk_head, entry, info):
140 if self.stop_on_error(ec):
142 info["timestamp"] = blk_head.timestamp
143 info["block_hash"] = str(bitcoin.hash_block_header(blk_head))
144 tx_hash = bitcoin.hash_digest(info["tx_hash"])
145 # Now load the actual main transaction for this input or output
146 self.chain.fetch_transaction(tx_hash,
147 self.wrap(self.load_tx, _1, _2, entry, info))
149 def load_tx(self, ec, tx, entry, info):
150 if self.stop_on_error(ec):
152 # List of output addresses
154 for tx_out in tx.outputs:
155 address = bitcoin.payment_address()
156 # Attempt to extract address from output script
157 if address.extract(tx_out.output_script):
158 outputs.append(address.encoded())
160 # ... otherwise append "Unknown"
161 outputs.append("Unknown")
162 info["outputs"] = outputs
163 # For the inputs, we need the originator address which has to
164 # be looked up in the blockchain.
165 # Create list of Nones and then populate it.
166 # Loading has finished when list is no longer all None.
167 info["inputs"] = [None for i in tx.inputs]
168 # If this transaction was loaded for an input, then we already
169 # have a source address for at least one input.
170 if info["is_input"] == 1:
171 info["inputs"][info["index"]] = self.address
173 our_output = tx.outputs[info["index"]]
174 info["value"] = our_output.value
175 # Save serialised output script in case this output is unspent
177 entry.raw_output_script = \
178 str(bitcoin.save_script(our_output.output_script))
179 # If all the inputs are loaded
180 if self.inputs_all_loaded(info["inputs"]):
181 # We are the sole input
182 assert(info["is_input"] == 1)
184 entry.input_loaded = info
185 self.finish_if_done()
186 # Load the previous_output for every input so we can get
188 for input_index, tx_input in enumerate(tx.inputs):
189 if info["is_input"] == 1 and info["index"] == input_index:
191 prevout = tx_input.previous_output
192 self.chain.fetch_transaction(prevout.hash,
193 self.wrap(self.load_input_tx, _1, _2,
194 prevout.index, entry, info, input_index))
196 def inputs_all_loaded(self, info_inputs):
197 return not [empty_in for empty_in in info_inputs if empty_in is None]
199 def load_input_tx(self, ec, tx, output_index, entry, info, input_index):
200 if self.stop_on_error(ec):
202 # For our input, we load the previous tx so we can get the
203 # corresponding output.
204 # We need the output to extract the address.
205 script = tx.outputs[output_index].output_script
206 address = bitcoin.payment_address()
207 if address.extract(script):
208 info["inputs"][input_index] = address.encoded()
210 info["inputs"][input_index] = "Unknown"
211 # If all the inputs are loaded, then we have finished loading
212 # the info for this input-output entry pair
213 if self.inputs_all_loaded(info["inputs"]):
215 if info["is_input"] == 1:
216 entry.input_loaded = info
218 entry.output_loaded = info
219 self.finish_if_done()
221 if __name__ == "__main__":
222 def blockchain_started(ec, chain):
223 print "Blockchain initialisation:", ec
226 for k, v in line.iteritems():
228 print begin, " " * (12 - len(begin)), v
231 service = bitcoin.async_service(1)
232 prefix = "/home/genjix/libbitcoin/database"
233 chain = bitcoin.bdb_blockchain(service, prefix, blockchain_started)
234 address = "1Pbn3DLXfjqF1fFV9YPdvpvyzejZwkHhZE"
235 print "Looking up", address
236 local_service = bitcoin.AsyncService()
237 h = History(local_service, chain)
238 h.start(address, finish)