async def from_dict(cls, block): transactions = [] for txn in block.get('transactions'): # TODO: do validity checking for coinbase transactions if str(P2PKHBitcoinAddress.from_pubkey(bytes.fromhex(block.get('public_key')))) in [x['to'] for x in txn.get('outputs', '')] and len(txn.get('outputs', '')) == 1 and not txn.get('inputs') and not txn.get('relationship'): txn['coinbase'] = True else: txn['coinbase'] = False transactions.append(Transaction.from_dict(txn)) if block.get('special_target', 0) == 0: block['special_target'] = block.get('target') return await cls.init_async( version=block.get('version'), block_time=block.get('time'), block_index=block.get('index'), public_key=block.get('public_key'), prev_hash=block.get('prevHash'), nonce=block.get('nonce'), transactions=transactions, block_hash=block.get('hash'), merkle_root=block.get('merkleRoot'), signature=block.get('id'), special_min=block.get('special_min'), header=block.get('header', ''), target=int(block.get('target'), 16), special_target=int(block.get('special_target', 0), 16) )
async def newtxn(self, body, stream): payload = body.get('params', {}) if (not payload.get('transaction')): return txn = Transaction.from_dict(payload.get('transaction')) try: await txn.verify() except: return await self.config.mongo.async_db.miner_transactions.replace_one( {'id': txn.transaction_signature}, txn.to_dict(), upsert=True) if stream.peer.protocol_version > 2: await self.write_result(stream, 'newtxn_confirmed', body.get('params', {}), body['id']) async for peer_stream in self.config.peer.get_sync_peers(): if peer_stream.peer.rid == stream.peer.rid: continue await self.write_params(peer_stream, 'newtxn', payload) if peer_stream.peer.protocol_version > 1: self.retry_messages[(peer_stream.peer.rid, 'newtxn', txn.transaction_signature)] = body.get( 'params', {})
async def newtxn_confirmed(self, body, stream): result = body.get('result', {}) transaction = Transaction.from_dict(result.get('transaction')) if (stream.peer.rid, 'newtxn', transaction.transaction_signature) in self.retry_messages: del self.retry_messages[(stream.peer.rid, 'newtxn', transaction.transaction_signature)]
async def post(self): try: ns = json.loads(self.request.body.decode('utf-8')) except: return self.render_as_json({ 'status': 'error', 'message': 'invalid request body' }) try: nstxn = Transaction.from_dict(ns['txn']) except: return self.render_as_json({ 'status': 'error', 'message': 'invalid transaction' }) try: peer = Peer(ns['peer']['host'], ns['peer']['port']) except: return self.render_as_json({ 'status': 'error', 'message': 'invalid peer' }) existing = await self.config.mongo.async_db.name_server.find_one({ 'rid': nstxn.rid, 'requester_rid': nstxn.requester_rid, 'requested_rid': nstxn.requested_rid, 'peer_str': peer.to_string(), }) if not existing: await self.config.mongo.async_db.name_server.insert_one({ 'rid': nstxn.rid, 'requester_rid': nstxn.requester_rid, 'requested_rid': nstxn.requested_rid, 'peer_str': peer.to_string(), 'peer': peer.to_dict(), 'txn': nstxn.to_dict() }) tb = NSBroadcaster(self.config) await tb.ns_broadcast_job(nstxn) return self.render_as_json({'status': 'success'})
def get_transaction_by_id(self, id, instance=False, give_block=False, include_fastgraph=False, inc_mempool=False): from yadacoin.core.transaction import Transaction res = self.mongo.db.blocks.find({"transactions.id": id}) if res.count(): for block in res: if give_block: return block for txn in block['transactions']: if txn['id'] == id: if instance: try: return FastGraph.from_dict(block['index'], txn) except: return Transaction.from_dict(txn) else: return txn if inc_mempool: res2 = self.mongo.db.miner_transactions.find_one({"id": id}) if res2: if give_block: raise Exception( 'Cannot give block for mempool transaction') if instance: return Transaction.from_dict(res2) else: return res2 return None else: # fix for bug when unspent cache returns an input # that has been removed from the chain self.mongo.db.unspent_cache.remove({}) return None
def check_rid_txn_fully_spent(cls, config, rid_txn, address, index): from yadacoin.core.transaction import Transaction rid_txn = Transaction.from_dict(rid_txn) spending_txn = rid_txn.used_as_input(rid_txn.transaction_signature) if spending_txn: for output in spending_txn['outputs']: if output['to'] == address and output['value'] == 0: return True # now we can create a duplicate relationship x = config.BU.get_transaction_by_id(spending_txn['id'], instance=True) result = cls.check_rid_txn_fully_spent(config, x.to_dict(), address, index) if result: return True return False else: return False # hasn't been spent to zero yet
async def newtxn(self, body, stream): payload = body.get('params', {}) if (not payload.get('transaction')): return txn = Transaction.from_dict(payload.get('transaction')) try: await txn.verify() except: return to_store = txn.to_dict() to_store['sent_to'] = [stream.peer.to_dict()] await self.config.mongo.async_db.miner_transactions.replace_one( {'id': txn.transaction_signature}, to_store, upsert=True, ) async for peer_stream in self.config.peer.get_sync_peers(): if peer_stream.peer.rid == stream.peer.rid: continue await self.write_params(peer_stream, 'newtxn', payload)
async def generate( cls, transactions=None, public_key=None, private_key=None, force_version=None, index=0, force_time=None, prev_hash=None, nonce=None, target=CHAIN.MAX_TARGET ): config = get_config() app_log = getLogger("tornado.application") if force_version is None: version = CHAIN.get_version_for_height(index) else: version = force_version if force_time: xtime = str(int(force_time)) else: xtime = str(int(time.time())) index = int(index) if index == 0: prev_hash = '' elif prev_hash is None and index != 0: prev_hash = LatestBlock.block.hash transactions = transactions or [] transaction_objs = [] fee_sum = 0.0 used_sigs = [] used_inputs = {} for txn in transactions: try: if isinstance(txn, Transaction): transaction_obj = txn else: transaction_obj = Transaction.from_dict(txn) if transaction_obj.transaction_signature in used_sigs: print('duplicate transaction found and removed') continue await transaction_obj.verify() used_sigs.append(transaction_obj.transaction_signature) except: raise InvalidTransactionException("invalid transactions") try: if int(index) > CHAIN.CHECK_TIME_FROM and (int(transaction_obj.time) > int(xtime) + CHAIN.TIME_TOLERANCE): config.mongo.db.miner_transactions.remove({'id': transaction_obj.transaction_signature}, multi=True) app_log.debug("Block embeds txn too far in the future {} {}".format(xtime, transaction_obj.time)) continue if transaction_obj.inputs: failed = False used_ids_in_this_txn = [] for x in transaction_obj.inputs: if config.BU.is_input_spent(x.id, transaction_obj.public_key): failed = True if x.id in used_ids_in_this_txn: failed = True if (x.id, transaction_obj.public_key) in used_inputs: failed = True used_inputs[(x.id, transaction_obj.public_key)] = transaction_obj used_ids_in_this_txn.append(x.id) if failed: continue transaction_objs.append(transaction_obj) fee_sum += float(transaction_obj.fee) except Exception as e: await config.mongo.async_db.miner_transactions.delete_many({'id': transaction_obj.transaction_signature}) config.app_log.debug('Exception {}'.format(e)) continue block_reward = CHAIN.get_block_reward(index) coinbase_txn = await Transaction.generate( public_key=public_key, private_key=private_key, outputs=[{ 'value': block_reward + float(fee_sum), 'to': str(P2PKHBitcoinAddress.from_pubkey(bytes.fromhex(public_key))) }], coinbase=True ) transaction_objs.append(coinbase_txn) transactions = transaction_objs block = await cls.init_async( version=version, block_time=xtime, block_index=index, prev_hash=prev_hash, transactions=transactions, public_key=public_key, target=target ) txn_hashes = block.get_transaction_hashes() block.set_merkle_root(txn_hashes) block.target = target block.header = block.generate_header() if nonce: block.nonce = str(nonce) block.hash = block.generate_hash_from_header( block.index, block.header, str(block.nonce) ) block.signature = TU.generate_signature(block.hash, private_key) return block
async def post(self): self.get_base_graph( ) # TODO: did this to set bulletin_secret, refactor this items = json.loads(self.request.body.decode('utf-8')) if not isinstance(items, list): items = [ items, ] else: items = [item for item in items] transactions = [] for txn in items: transaction = Transaction.from_dict(txn) try: await transaction.verify() except InvalidTransactionException: await self.config.mongo.async_db.failed_transactions.insert_one( { 'exception': 'InvalidTransactionException', 'txn': txn }) print('InvalidTransactionException') return 'InvalidTransactionException', 400 except InvalidTransactionSignatureException: print('InvalidTransactionSignatureException') await self.config.mongo.async_db.failed_transactions.insert_one( { 'exception': 'InvalidTransactionSignatureException', 'txn': txn }) return 'InvalidTransactionSignatureException', 400 except MissingInputTransactionException: pass except: raise print('uknown error') return 'uknown error', 400 transactions.append(transaction) for x in transactions: if x.rid == self.rid and x.dh_public_key: me_pending_exists = await self.config.mongo.async_db.miner_transactions.find_one( { 'public_key': self.config.public_key, 'rid': self.rid, 'dh_public_key': { '$exists': True } }) me_blockchain_exists = await self.config.mongo.async_db.blocks.find_one( { 'public_key': self.config.public_key, 'rid': self.rid, 'dh_public_key': { '$exists': True } }) if not me_pending_exists and not me_blockchain_exists: created_relationship = await self.create_relationship( self.bulletin_secret, self.username, self.to) if isinstance(created_relationship, Transaction): await self.config.mongo.async_db.miner_transactions.insert_one( created_relationship.to_dict()) created_relationship.relationship = created_relationship.relationship await self.config.mongo.async_db.name_server.insert_one( { 'rid': created_relationship.rid, 'requester_rid': created_relationship.requester_rid, 'requested_rid': created_relationship.requested_rid, 'peer_str': 'me', 'peer': { 'host': 'me', 'port': 0 }, 'txn': created_relationship.to_dict() }) tb = NSBroadcaster(self.config) await tb.ns_broadcast_job(created_relationship) else: self.app_log.debug( 'relationship creation failed for NS: {}'.format( created_relationship)) pending_exists = await self.config.mongo.async_db.miner_transactions.find_one( { 'public_key': x.public_key, 'rid': self.rid, 'dh_public_key': { '$exists': True } }) blockchain_exists = await self.config.mongo.async_db.blocks.find_one( { 'public_key': x.public_key, 'rid': self.rid, 'dh_public_key': { '$exists': True } }) if pending_exists or blockchain_exists: continue if x.dh_public_key: dup_check_count = await self.config.mongo.async_db.miner_transactions.count_documents( { 'dh_public_key': { '$exists': True }, 'rid': x.rid, 'requester_rid': x.requester_rid, 'requested_rid': x.requested_rid, 'public_key': x.public_key }) if dup_check_count: self.app_log.debug( 'found duplicate tx for rid set {}'.format( x.transaction_signature)) return await self.config.mongo.async_db.miner_transactions.insert_one( x.to_dict()) return self.render_as_json(items)
async def get_pending_transactions(self): transaction_objs = [] used_sigs = [] async for txn in self.mongo.async_db.miner_transactions.find().sort([ ('fee', -1) ]): try: if isinstance(txn, Transaction): transaction_obj = txn elif isinstance(txn, dict): transaction_obj = Transaction.from_dict(txn) else: self.config.app_log.warning( 'transaction unrecognizable, skipping') continue await transaction_obj.verify() if transaction_obj.transaction_signature in used_sigs: self.config.app_log.warning( 'duplicate transaction found and removed') continue used_sigs.append(transaction_obj.transaction_signature) failed1 = False failed2 = False used_ids_in_this_txn = [] async for x in self.get_inputs(transaction_obj.inputs): is_input_spent = await self.config.BU.is_input_spent( x.id, transaction_obj.public_key) if is_input_spent: failed1 = True if x.id in used_ids_in_this_txn: failed2 = True used_ids_in_this_txn.append(x.id) if failed1: self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.config.app_log.warning( 'transaction removed: input presumably spent already, not in unspent outputs {}' .format(transaction_obj.transaction_signature)) self.mongo.db.failed_transactions.insert({ 'reason': 'input presumably spent already', 'txn': transaction_obj.to_dict() }) elif failed2: self.config.app_log.warning( 'transaction removed: using an input used by another transaction in this block {}' .format(transaction_obj.transaction_signature)) self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'using an input used by another transaction in this block', 'txn': transaction_obj.to_dict() }) else: transaction_objs.append(transaction_obj) except MissingInputTransactionException as e: self.config.app_log.warning( 'MissingInputTransactionException: transaction removed') self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'MissingInputTransactionException', 'txn': transaction_obj.to_dict() }) except InvalidTransactionSignatureException as e: self.config.app_log.warning( 'InvalidTransactionSignatureException: transaction removed' ) self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'InvalidTransactionSignatureException', 'txn': transaction_obj.to_dict() }) except InvalidTransactionException as e: self.config.app_log.warning( 'InvalidTransactionException: transaction removed') self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'InvalidTransactionException', 'txn': transaction_obj.to_dict() }) except TransactionInputOutputMismatchException as e: self.config.app_log.warning( 'TransactionInputOutputMismatchException: transaction removed' ) self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'TransactionInputOutputMismatchException', 'txn': transaction_obj.to_dict() }) except TotalValueMismatchException as e: self.config.app_log.warning( 'TotalValueMismatchException: transaction removed') self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'TotalValueMismatchException', 'txn': transaction_obj.to_dict() }) except Exception as e: self.config.app_log.warning(format_exc()) self.mongo.db.miner_transactions.remove( {'id': transaction_obj.transaction_signature}) self.mongo.db.failed_transactions.insert({ 'reason': 'Unhandled exception', 'error': format_exc() }) return transaction_objs
async def get_payload_txn(self, payload): txn = None if payload.get('transaction'): txn = Transaction.from_dict(payload.get('transaction')) return txn
async def do_payout_for_blocks(self, blocks): # check if we already paid out outputs = {} coinbases = [] for block in blocks: if self.config.debug: self.app_log.debug('do_payout_for_blocks begin loop {}'.format( block.index)) already_used = await self.already_used(block.get_coinbase()) if already_used: await self.config.mongo.async_db.shares.delete_many( {'index': block.index}) continue if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed already_used {}'.format( block.index)) existing = await self.config.mongo.async_db.share_payout.find_one( {'index': block.index}) if existing: pending = await self.config.mongo.async_db.miner_transactions.find_one( {'inputs.id': block.get_coinbase().transaction_signature}) if pending: return else: # rebroadcast transaction = Transaction.from_dict(existing['txn']) await self.config.mongo.async_db.miner_transactions.insert_one( transaction.to_dict()) await self.broadcast_transaction(transaction) return if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed existing {}'.format( block.index)) try: shares = await self.get_share_list_for_height(block.index) if not shares: continue except KeyError as e: self.app_log.warning(e) return except Exception as e: self.app_log.warning(e) return if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed get_share_list_for_height {}'. format(block.index)) coinbase = block.get_coinbase() if coinbase.outputs[0].to != self.config.address: return if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed address compare {}'.format( block.index)) pool_take = self.config.pool_take total_pool_take = coinbase.outputs[0].value * pool_take total_payout = coinbase.outputs[0].value - total_pool_take coinbases.append(coinbase) if self.config.debug: self.app_log.debug( 'do_payout_for_blocks coinbases so far {}'.format( coinbases)) if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed coinbase calcs {}'.format( block.index)) for address, x in shares.items(): if self.config.debug: self.app_log.debug( 'do_payout_for_blocks shares loop {}'.format( block.index)) exists = await self.config.mongo.async_db.share_payout.find_one( { 'index': block.index, 'txn.outputs.to': address }) if exists: raise PartialPayoutException( 'this index has been partially paid out.') if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed shares exists {}'.format( block.index)) if address not in outputs: outputs[address] = 0.0 payout = total_payout * x['payout_share'] outputs[address] += payout if self.config.debug: self.app_log.debug( 'do_payout_for_blocks passed adding payout to outputs {}' .format(block.index)) outputs_formatted = [] for address, output in outputs.items(): outputs_formatted.append({'to': address, 'value': output}) if hasattr(self.config, 'pool_payout_address_override' ) and self.config.pool_payout_address_override: outputs_formatted.append({ 'to': self.config.pool_payout_address_override, 'value': total_pool_take }) if self.config.debug: self.app_log.debug( 'do_payout_for_blocks done formatting outputs {}'.format([{ 'id': coinbase.transaction_signature } for coinbase in coinbases])) try: transaction = await Transaction.generate( fee=0.0001, public_key=self.config.public_key, private_key=self.config.private_key, inputs=[{ 'id': coinbase.transaction_signature } for coinbase in coinbases], outputs=outputs_formatted, ) self.app_log.debug("transaction generated: {}".format( transaction.transaction_signature)) except NotEnoughMoneyException as e: if self.config.debug: self.app_log.debug("not enough money yet") self.app_log.debug(e) return except Exception as e: if self.config.debug: self.app_log.debug(e) try: await transaction.verify() except Exception as e: if self.config.debug: self.app_log.debug(e) raise self.app_log.debug('transaction verified') await self.config.mongo.async_db.miner_transactions.insert_one( transaction.to_dict()) await self.config.mongo.async_db.share_payout.insert_one({ 'index': block.index, 'txn': transaction.to_dict() })
async def route(self, body): # our peer SHOULD only ever been a service provider if we're offering a websocket but we'll give other options here params = body.get('params') transaction = Transaction.from_dict(params['transaction']) await self.config.mongo.async_db.miner_transactions.replace_one( {'id': transaction.transaction_signature}, transaction.to_dict(), upsert=True) if isinstance(self.config.peer, Seed): pass # for rid, peer_stream in self.config.nodeServer.inbound_streams[Seed.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) # for rid, peer_stream in self.config.nodeServer.inbound_streams[SeedGateway.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) # for rid, peer_stream in self.config.nodeClient.outbound_streams[Seed.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) elif isinstance(self.config.peer, SeedGateway): pass # for rid, peer_stream in self.config.nodeServer.inbound_streams[ServiceProvider.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) # for rid, peer_stream in self.config.nodeClient.outbound_streams[Seed.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) elif isinstance(self.config.peer, ServiceProvider): pass # for rid, peer_stream in self.config.nodeServer.inbound_streams[User.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) # for rid, peer_stream in self.config.nodeClient.outbound_streams[SeedGateway.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) if transaction.requested_rid in self.config.websocketServer.inbound_streams[ Group.__name__]: for rid, peer_stream in self.config.websocketServer.inbound_streams[ Group.__name__][transaction.requested_rid].items(): if rid == transaction.requester_rid: continue await peer_stream.write_params('route', params) if transaction.requested_rid in self.config.websocketServer.inbound_streams[ User.__name__]: peer_stream = self.config.websocketServer.inbound_streams[ User.__name__][transaction.requested_rid] if peer_stream.peer.rid != transaction.requester_rid: await peer_stream.write_params('route', params) if 'group' in params: group = Group.from_dict({ 'host': None, 'port': None, 'identity': params['group'] }) params2 = params.copy() to = User.from_dict({ 'host': None, 'port': None, 'identity': params['to'] }) peer_stream = self.config.websocketServer.inbound_streams[ Group.__name__][group.rid].get(to.rid) if peer_stream: await peer_stream.write_params('route', params2) elif isinstance(self.config.peer, User): pass # for rid, peer_stream in self.config.nodeClient.outbound_streams[ServiceProvider.__name__].items(): # await BaseRPC().write_params(peer_stream, 'route', params) else: self.config.app_log.error( 'inbound peer is not defined, disconnecting') self.close() return {} await self.write_result('route_server_confirm', {}, body=body)