def confirm_bitcoin_tx_online(self, hash, sender_gid, network): """ confirm bitcoin transaction using default online Samourai API instance Usage: confirm_bitcoin_tx tx_id gid network """ if network == 't' : url = "https://api.samourai.io/test/v2/tx/" + hash ## default testnet txtenna-server else : url = "https://api.samourai.io/v2/tx/" + hash ## default txtenna-server try: r = requests.get(url) ## print(r.text) while r.status_code != 200: sleep(60) # sleep for a minute r = requests.get(url) ## send zero-conf message back to tx sender rObj = TxTennaSegment('', '', tx_hash=hash, block=0) # arg = str(sender_gid) + ' ' + rObj.serialize_to_json() arg = str(sender_gid) + " Transaction " + hash + " added to the mempool." self.do_send_private(arg) print("\nSent to GID: " + str(sender_gid) + ": Transaction " + hash + " added to the mempool.") r_text = "".join(r.text.split()) # remove whitespace obj = json.loads(r_text) while not 'block' in obj.keys(): sleep(60) # sleep for a minute r = requests.get(url) r_text = "".join(r.text.split()) obj = json.loads(r_text) ## send block height message back to tx sender blockheight = obj['block']['height'] rObj = TxTennaSegment('', '', tx_hash=hash, block=blockheight) # arg = str(sender_gid) + ' ' + rObj.serialize_to_json() # TODO: merkle proof for transaction arg = str(sender_gid) + " Transaction " + hash + " confirmed in block " + str(blockheight) + "." self.do_send_private(arg) print("\nSent to GID: " + str(sender_gid) + ": Transaction " + hash + " confirmed in block " + str(blockheight) + ".") except: traceback.print_exc()
def test_put_new_payload_out_of_order(self, segments): sg0 = TxTennaSegment("1005", "the payload data for segment 0", tx_hash="eeeeee", sequence_num=0, segment_count=3) sg1 = TxTennaSegment("1005", "the payload data for segment 1", sequence_num=1) segments.put(sg1) segments.put(sg0) gsg = segments.get("1005") gsg2 = segments.get_by_transaction_id("eeeeee") assert gsg2[0].payload_id == gsg[0].payload_id assert len(gsg) == 2 assert gsg[0].sequence_num == 0 assert gsg[1].sequence_num == 1
def test_deserialize_non_first_from_json(self): json_ser = '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "c": 1}' segment = TxTennaSegment.deserialize_from_json(json_ser) assert segment.payload_id == "1000" assert segment.sequence_num == 1 assert segment.segment_count is None assert segment.testnet is False assert segment.payload == "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc"
def test_constructor_defaults(self): segment = TxTennaSegment("1000", "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc") assert segment.tx_hash is None assert segment.payload_id == "1000" assert segment.payload == "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc" assert segment.sequence_num == 0 assert not segment.testnet assert segment.segment_count is None
def test_put_new_payload(self, segments): sg = TxTennaSegment("1004", "the payload data for segment 0", tx_hash="789fff", sequence_num=0, segment_count=3) segments.put(sg) gsg = segments.get("1004") assert gsg is not None assert gsg[0].tx_hash == "789fff"
def segments(): payload1seg1 = TxTennaSegment("1000", "ra]?=rb3hXB09d)awc6WatLS8ir", tx_hash="abc123", sequence_num=0, segment_count=3, testnet=False) payload1seg2 = TxTennaSegment("1000", "lUG[Cv}xE)z/M$szxIn^x(mX", sequence_num=1, testnet=False) payload1seg3 = TxTennaSegment("1000", "z!pa<wN/T@wfsiEx(4ZgBrCglAV^XSzFKp", sequence_num=2, testnet=False) payload2seg1 = TxTennaSegment("1001", "ra]?=B9z5kz6iLlazts{wmYo]Bz(wiA+6klB-N", tx_hash="def456", sequence_num=0, segment_count=2, testnet=False) payload3seg2 = TxTennaSegment( "1002", "vS==nBzbkdxLzKfz/QaCz!pb7x<(9av%8*jwO#0(wPF#dB0b7qy?$kjw/$M6wNP7ZwPF#jw/#hevrrSlA=(CB", sequence_num=1, testnet=False) # payload3seg1 = "ra]?=v}xL1A:-=bvQTd&az(mxBrC47aARTDB97#7az#ataARJAwmYjUB7DFoxK@q@B-IIlzEW@y" storage = SegmentStorage() storage.put(payload1seg1) storage.put(payload1seg2) storage.put(payload1seg3) storage.put(payload2seg1) storage.put(payload3seg2) return storage
def broadcast_message_files(self, directory, filenames): for filename in filenames: print("Broadcasting ",directory+"/"+filename) f = open(directory+"/"+filename,'r') message_data = f.read() f.close ## binary to ascii encoding and strip out newlines encoded = zlib.compress(message_data, 9).encode('base64').replace('\n','') print("[\n" + encoded.decode() + "\n]") gid = self.api_thread.gid.gid_val segments = TxTennaSegment.tx_to_segments(gid, encoded, filename, str(self.messageIdx), "d", False) for seg in segments : self.do_send_broadcast(seg.serialize_to_json()) sleep(10) self.messageIdx = (self.messageIdx+1) % 9999
def do_mesh_broadcast_rawtx(self, rem): """ Broadcast the raw hex of a Bitcoin transaction and its transaction ID over mainnet or testnet. A local copy of txtenna-server must be configured to support the selected network. Usage: mesh_broadcast_tx RAW_HEX TX_ID NETWORK(m|t) eg. txTenna> mesh_broadcast_rawtx 01000000000101bf6c3ed233e8700b42c1369993c2078780015bab7067b9751b7f49f799efbffd0000000017160014f25dbf0eab0ba7e3482287ebb41a7f6d361de6efffffffff02204e00000000000017a91439cdb4242013e108337df383b1bf063561eb582687abb93b000000000017a9148b963056eedd4a02c91747ea667fc34548cab0848702483045022100e92ce9b5c91dbf1c976d10b2c5ed70d140318f3bf2123091d9071ada27a4a543022030c289d43298ca4ca9d52a4c85f95786c5e27de5881366d9154f6fe13a717f3701210204b40eff96588033722f487a52d39a345dc91413281b31909a4018efb330ba2600000000 94406beb94761fa728a2cde836ca636ecd3c51cbc0febc87a968cb8522ce7cc1 m """ ## TODO: test Z85 binary encoding and add as an option (strHexTx, strHexTxHash, network) = rem.split(" ") gid = self.api_thread.gid.gid_val segments = TxTennaSegment.tx_to_segments(gid, strHexTx, strHexTxHash, str(self.messageIdx), network, False) for seg in segments : self.do_send_broadcast(seg.serialize_to_json()) sleep(10) self.messageIdx = (self.messageIdx+1) % 9999
def handle_cbor_message(self, sender_gid, protocol_msg): # TODO: 1a) concatonate segments and send as new transaction to block explorer # TODO: 1b) send acknowledgement back to Signal Mesh as a text message # TODO: 2a) monitor for transaction to be confirmed in a block # TODO: 2b) send blockchain confirmation back to Signal Mesh as a text message txtenna_json = self.cbor_to_txtenna_json(protocol_msg) segment = TxTennaSegment.deserialize_from_json(txtenna_json) self.segment_storage.put(segment) network = self.segment_storage.get_network(segment.payload_id) ## process incoming transaction confirmation from another server if (segment.block != None): if (segment.block > 0): print("\nTransaction " + segment.payload_id + " confirmed in block " + str(segment.block)) elif (segment.block is 0): print("\nTransaction " + segment.payload_id + " added to the the mem pool") elif (network is 'd'): ## process message data if (self.segment_storage.is_complete(segment.payload_id)): filename = self.segment_storage.get_transaction_id(segment.payload_id) t = Thread(target=self.receive_message_from_gateway, args=(filename,)) t.start() else: ## process incoming tx segment if not self.local_bitcoind : headers = {u'content-type': u'application/json'} url = "https://api.samouraiwallet.com/v2/txtenna/segments" ## default txtenna-server r = requests.post(url, headers= headers, data=txtenna_json) print(r.text) if (self.segment_storage.is_complete(segment.payload_id)): # sender_gid = message.sender.gid_val tx_id = self.segment_storage.get_transaction_id(segment.payload_id) ## check for confirmed transaction in a new thread if (self.local_bitcoind) : t = Thread(target=self.confirm_bitcoin_tx_local, args=(tx_id, sender_gid)) else : t = Thread(target=self.confirm_bitcoin_tx_online, args=(tx_id, sender_gid, network)) t.start()
def handle_message(self, message): """ handle a txtenna message received over the mesh network Usage: handle_message message """ payload = str(message.payload.message) print("received transaction payload: " + payload) segment = TxTennaSegment.deserialize_from_json(payload) self.segment_storage.put(segment) network = self.segment_storage.get_network(segment.payload_id) ## process incoming transaction confirmation from another server if (segment.block > 0): print("\nTransaction " + segment.payload_id + " confirmed in block " + str(segment.block)) elif (segment.block is 0): print("\nTransaction " + segment.payload_id + " added to the the mem pool") elif (network is 'd'): ## process message data if (self.segment_storage.is_complete(segment.payload_id)): filename = self.segment_storage.get_transaction_id(segment.payload_id) t = Thread(target=self.receive_message_from_gateway, args=(filename,)) t.start() else: ## process incoming tx segment if not self.local : headers = {u'content-type': u'application/json'} url = "https://api.samouraiwallet.com/v2/txtenna/segments" ## default txtenna-server r = requests.post(url, headers= headers, data=payload) print(r.text) if (self.segment_storage.is_complete(segment.payload_id)): sender_gid = message.sender.gid_val tx_id = self.segment_storage.get_transaction_id(segment.payload_id) ## check for confirmed transaction in a new thread if (self.local) : t = Thread(target=self.confirm_bitcoin_tx_local, args=(tx_id, sender_gid)) else : t = Thread(target=self.confirm_bitcoin_tx_online, args=(tx_id, sender_gid, network)) t.start()
def test_segment_json_first_is_valid_explicit_zero_sequence(self): json_ser = '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "s": 1, "h": "123abc", "c": 0}' json_obj = json.loads(json_ser) assert TxTennaSegment.segment_json_is_valid(json_obj)
def confirm_bitcoin_tx_local(self, hash, sender_gid): """ Confirm bitcoin transaction using local bitcoind instance Usage: confirm_bitcoin_tx tx_id gid """ ## send transaction to local bitcond segments = self.segment_storage.get_by_transaction_id(hash) raw_tx = self.segment_storage.get_raw_tx(segments) ## pass hex string converted to bytes try : proxy1 = bitcoin.rpc.Proxy() raw_tx_bytes = x(raw_tx) tx = CMutableTransaction.stream_deserialize(BytesIO(raw_tx_bytes)) r1 = proxy1.sendrawtransaction(tx) except : print("Invalid Transaction! Could not send to network.") return ## try for 30 minutes to confirm the transaction for n in range(0, 30) : try : proxy2 = bitcoin.rpc.Proxy() r2 = proxy2.getrawtransaction(r1, True) ## send zero-conf message back to tx sender confirmations = r2.get('confirmations', 0) rObj = TxTennaSegment('', '', tx_hash=hash, block=confirmations) arg = str(sender_gid) + ' ' + rObj.serialize_to_json() self.do_send_private(arg) print("\nSent to GID: " + str(sender_gid) + ": Transaction " + hash + " added to the mempool.") break except IndexError: ## tx_id not yet in the global mempool, sleep for a minute and then try again sleep(60) continue ## wait for atleast one confirmation for m in range(0, 30): sleep(60) # sleep for a minute try : proxy3= bitcoin.rpc.Proxy() r3 = proxy3.getrawtransaction(r1, True) confirmations = r3.get('confirmations', 0) ## keep waiting until 1 or more confirmations if confirmations > 0: break except : ## unknown RPC error, but keep trying traceback.print_exc() if confirmations > 0 : ## send confirmations message back to tx sender if confirmations > 0 rObj = TxTennaSegment('', '', tx_hash=hash, block=confirmations) arg = str(sender_gid) + ' ' + rObj.serialize_to_json() self.do_send_private(arg) print("\nSent to GID: " + str(sender_gid) + ", Transaction " + hash + " confirmed in " + str(confirmations) + " blocks.") else : print("\CTransaction from GID: " + str(sender_gid) + ", Transaction " + hash + " not confirmed after 30 minutes.")
def test_segment_json_is_invalid_non_zero_sequence_with_segment_count(self): json_ser = '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "c": 1, "s": 2}' json_obj = json.loads(json_ser) assert not TxTennaSegment.segment_json_is_valid(json_obj)
def test_deserialize_invalid_segment_raises_exception(self): json_ser = '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "s": 1, "h": "123abc", "c": 1}' with pytest.raises(AttributeError): TxTennaSegment.deserialize_from_json(json_ser)
def test_serialize_testnet_segment(self): segment = TxTennaSegment("1000", "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", tx_hash="123abc", testnet=True, segment_count=2, sequence_num=1) json_ser = segment.serialize_to_json() assert json_ser == '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "c": 1, "n": "t"}'
def test_serialize_first_segment(self): segment = TxTennaSegment("1000", "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", tx_hash="123abc", testnet=False, segment_count=1, sequence_num=0) json_ser = segment.serialize_to_json() assert json_ser == '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "s": 1, "h": "123abc"}'
def test_repr(self): segment = TxTennaSegment("1000", "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", tx_hash="123abc", testnet=False, segment_count=1, sequence_num=0) segment_repr = repr(segment) assert segment_repr == '{"i": "1000", "t": "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", "s": 1, "h": "123abc"}'
def test_str(self): segment = TxTennaSegment("1000", "w]8f<vRG}fayY4]vRG}fayYm#vRG}fayYnc", tx_hash="123abc", testnet=False, segment_count=1, sequence_num=0) segment_str = str(segment) assert segment_str == "Tx 123abc Part 0"