X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=backends%2Flibbitcoin%2Fcomposed.py;fp=backends%2Flibbitcoin%2Fcomposed.py;h=9df57a0b2972853e2665e4cb712b9229a38531d5;hb=2e3252aa7976d1ddd11b333a1c5c5fa4fb3bb338;hp=0000000000000000000000000000000000000000;hpb=036b0bc5d32a841a56c304a6da79668944d714a4;p=electrum-server.git diff --git a/backends/libbitcoin/composed.py b/backends/libbitcoin/composed.py new file mode 100644 index 0000000..9df57a0 --- /dev/null +++ b/backends/libbitcoin/composed.py @@ -0,0 +1,194 @@ +import bitcoin +import threading +import time + +class ExpiryQueue(threading.Thread): + + def __init__(self): + self.lock = threading.Lock() + self.items = [] + threading.Thread.__init__(self) + self.daemon = True + + def run(self): + # Garbage collection + while True: + with self.lock: + self.items = [i for i in self.items if not i.stopped()] + time.sleep(0.1) + + def add(self, item): + with self.lock: + self.items.append(item) + +expiry_queue = ExpiryQueue() + +class StatementLine: + + def __init__(self, output_point): + self.lock = threading.Lock() + self.output_point = output_point + self.output_loaded = None + self.input_point = None + self.input_loaded = None + self.raw_output_script = None + + def is_loaded(self): + with self.lock: + if self.output_loaded is None: + return False + elif (self.input_point is not False and + self.input_loaded is None): + return False + return True + +class PaymentHistory: + + def __init__(self, chain): + self.chain = chain + self.lock = threading.Lock() + self.statement = [] + self._stopped = False + + def run(self, address, handle_finish): + self.address = address + self.handle_finish = handle_finish + + pubkey_hash = bitcoin.address_to_short_hash(address) + self.chain.fetch_outputs(pubkey_hash, self.start_loading) + + def start_loading(self, ec, output_points): + with self.lock: + for outpoint in output_points: + statement_line = StatementLine(outpoint) + self.statement.append(statement_line) + self.chain.fetch_spend(outpoint, + bitcoin.bind(self.load_spend, + bitcoin._1, bitcoin._2, statement_line)) + self.load_tx_info(outpoint, statement_line, False) + + def load_spend(self, ec, inpoint, statement_line): + with statement_line.lock: + if ec: + statement_line.input_point = False + else: + statement_line.input_point = inpoint + self.finish_if_done() + if not ec: + self.load_tx_info(inpoint, statement_line, True) + + def finish_if_done(self): + with self.lock: + if any(not line.is_loaded() for line in self.statement): + return + result = [] + for line in self.statement: + if line.input_point: + line.input_loaded["value"] = -line.output_loaded["value"] + result.append(line.input_loaded) + else: + line.output_loaded["raw_output_script"] = \ + line.raw_output_script + result.append(line.output_loaded) + self.handle_finish(result) + self.stop() + + def stop(self): + with self.lock: + self._stopped = True + + def stopped(self): + with self.lock: + return self._stopped + + def load_tx_info(self, point, statement_line, is_input): + info = {} + info["tx_hash"] = str(point.hash) + info["index"] = point.index + info["is_input"] = 1 if is_input else 0 + self.chain.fetch_transaction_index(point.hash, + bitcoin.bind(self.tx_index, bitcoin._1, bitcoin._2, bitcoin._3, + statement_line, info)) + + def tx_index(self, ec, block_depth, offset, statement_line, info): + info["height"] = block_depth + self.chain.fetch_block_header_by_depth(block_depth, + bitcoin.bind(self.block_header, bitcoin._1, bitcoin._2, + statement_line, info)) + + def block_header(self, ec, blk_head, statement_line, info): + info["timestamp"] = blk_head.timestamp + info["block_hash"] = str(bitcoin.hash_block_header(blk_head)) + tx_hash = bitcoin.hash_digest(info["tx_hash"]) + self.chain.fetch_transaction(tx_hash, + bitcoin.bind(self.load_tx, bitcoin._1, bitcoin._2, + statement_line, info)) + + def load_tx(self, ec, tx, statement_line, info): + outputs = [] + for tx_out in tx.outputs: + script = tx_out.output_script + if script.type() == bitcoin.payment_type.pubkey_hash: + pkh = bitcoin.short_hash(str(script.operations()[2].data)) + outputs.append(bitcoin.public_key_hash_to_address(pkh)) + else: + outputs.append("Unknown") + info["outputs"] = outputs + info["inputs"] = [None for i in range(len(tx.inputs))] + if info["is_input"] == 1: + info["inputs"][info["index"]] = self.address + else: + our_output = tx.outputs[info["index"]] + info["value"] = our_output.value + with statement_line.lock: + statement_line.raw_output_script = \ + str(bitcoin.save_script(our_output.output_script)) + if not [empty_in for empty_in in info["inputs"] if empty_in is None]: + # We have the sole input + assert(info["is_input"] == 1) + with statement_line.lock: + statement_line.input_loaded = info + self.finish_if_done() + for tx_idx, tx_in in enumerate(tx.inputs): + if info["is_input"] == 1 and info["index"] == tx_idx: + continue + self.chain.fetch_transaction(tx_in.previous_output.hash, + bitcoin.bind(self.load_input, bitcoin._1, bitcoin._2, + tx_in.previous_output.index, statement_line, info, tx_idx)) + + def load_input(self, ec, tx, index, statement_line, info, inputs_index): + script = tx.outputs[index].output_script + if script.type() == bitcoin.payment_type.pubkey_hash: + pkh = bitcoin.short_hash(str(script.operations()[2].data)) + info["inputs"][inputs_index] = \ + bitcoin.public_key_hash_to_address(pkh) + else: + info["inputs"][inputs_index] = "Unknown" + if not [empty_in for empty_in in info["inputs"] if empty_in is None]: + with statement_line.lock: + if info["is_input"] == 1: + statement_line.input_loaded = info + else: + statement_line.output_loaded = info + self.finish_if_done() + +def payment_history(chain, address, handle_finish): + ph = PaymentHistory(chain) + expiry_queue.add(ph) + ph.run(address, handle_finish) + +if __name__ == "__main__": + def finish(result): + print result + def last(ec, depth): + print "D:", depth + + service = bitcoin.async_service(1) + prefix = "/home/genjix/libbitcoin/database" + chain = bitcoin.bdb_blockchain(service, prefix) + chain.fetch_last_depth(last) + address = "1Pbn3DLXfjqF1fFV9YPdvpvyzejZwkHhZE" + print "Looking up", address + payment_history(chain, address, finish) + raw_input() +