def prepare_tx(self, key, receiver, blockstamp, amount, amount_base, message, currency): """ Prepare a simple Transaction document :param SigningKey key: the issuer of the transaction :param str receiver: the target of the transaction :param duniterpy.documents.BlockUID blockstamp: the blockstamp :param int amount: the amount sent to the receiver :param int amount_base: the amount base of the currency :param str message: the comment of the tx :param str currency: the target community :return: the transaction document :rtype: List[sakia.data.entities.Transaction] """ forged_tx = [] sources = [None]*41 while len(sources) > 40: result = self.tx_sources(int(amount), amount_base, currency, key.pubkey) sources = result[0] computed_outputs = result[1] overheads = result[2] # Fix issue #594 if len(sources) > 40: sources_value = 0 for s in sources[:39]: sources_value += s.amount * (10**s.base) sources_value, sources_base = reduce_base(sources_value, 0) chained_tx = self.prepare_tx(key, key.pubkey, blockstamp, sources_value, sources_base, "[CHAINED]", currency) forged_tx += chained_tx self._sources_processor.consume(sources) logging.debug("Inputs : {0}".format(sources)) inputs = self.tx_inputs(sources) unlocks = self.tx_unlocks(sources) outputs = self.tx_outputs(key.pubkey, receiver, computed_outputs, overheads) logging.debug("Outputs : {0}".format(outputs)) txdoc = TransactionDoc(10, currency, blockstamp, 0, [key.pubkey], inputs, unlocks, outputs, message, None) txdoc.sign([key]) self.commit_outputs_to_self(currency, key.pubkey, txdoc) time = self._blockchain_processor.time(currency) tx = Transaction(currency=currency, pubkey=key.pubkey, sha_hash=txdoc.sha_hash, written_block=0, blockstamp=blockstamp, timestamp=time, signatures=txdoc.signatures, issuers=[key.pubkey], receivers=[receiver], amount=amount, amount_base=amount_base, comment=txdoc.comment, txid=0, state=Transaction.TO_SEND, local=True, raw=txdoc.signed_raw()) forged_tx.append(tx) return forged_tx
async def initialize_transactions(self, connection, log_stream): """ Request transactions from the network to initialize data for a given pubkey :param sakia.data.entities.Connection connection: :param function log_stream: """ history_data = await self._bma_connector.get( connection.currency, bma.tx.history, req_args={'pubkey': connection.pubkey}) txid = 0 nb_tx = len(history_data["history"]["sent"]) + len( history_data["history"]["received"]) log_stream("Found {0} transactions".format(nb_tx)) transactions = [] for sent_data in history_data["history"]["sent"] + history_data[ "history"]["received"]: sent = TransactionDoc.from_bma_history(history_data["currency"], sent_data) log_stream("{0}/{1} transactions".format(txid, nb_tx)) try: tx = parse_transaction_doc(sent, connection.pubkey, sent_data["block_number"], sent_data["time"], txid) transactions.append(tx) self._repo.insert(tx) except sqlite3.IntegrityError: log_stream("Transaction already registered in database") await asyncio.sleep(0) txid += 1 return transactions
def restore_sources(self, pubkey, tx): """ Restore the sources of a cancelled tx :param sakia.entities.Transaction tx: """ txdoc = TransactionDoc.from_signed_raw(tx.raw) for offset, output in enumerate(txdoc.outputs): if output.conditions.left.pubkey == pubkey: source = Source(currency=self.currency, pubkey=pubkey, identifier=txdoc.sha_hash, type='T', noffset=offset, amount=output.amount, base=output.base) self._sources_processor.drop(source) for index, input in enumerate(txdoc.inputs): source = Source(currency=self.currency, pubkey=txdoc.issuers[0], identifier=input.origin_id, type=input.source, noffset=input.index, amount=input.amount, base=input.base) if source.pubkey == pubkey: self._sources_processor.insert(source)
def send_money(self, amount, sources, receiver, blockstamp, message): result = self.outputs_from_sources(amount, sources) inputs = result[0] computed_outputs = result[1] overheads = result[2] unlocks = [] for i, s in enumerate(sources): unlocks.append(Unlock(i, [SIGParameter(0)])) outputs = self.tx_outputs(receiver, computed_outputs, overheads) tx = Transaction(3, self.currency, blockstamp, 0, [self.key.pubkey], inputs, unlocks, outputs, message, None) tx.sign([self.key]) return tx
async def parse_transactions_history(self, connections, start, end): """ Request transactions from the network to initialize data for a given pubkey :param List[sakia.data.entities.Connection] connections: the list of connections found by tx parsing :param int start: the first block :param int end: the last block """ transfers_changed = [] new_transfers = {} for connection in connections: txid = 0 new_transfers[connection] = [] history_data = await self._bma_connector.get(self.currency, bma.tx.blocks, req_args={'pubkey': connection.pubkey, 'start': start, 'end': end}) for tx_data in history_data["history"]["sent"]: for tx in [t for t in self._transactions_processor.awaiting(self.currency)]: if self._transactions_processor.run_state_transitions(tx, tx_data["hash"], tx_data["block_number"]): transfers_changed.append(tx) self._logger.debug("New transaction validated : {0}".format(tx.sha_hash)) for tx_data in history_data["history"]["received"]: tx_doc = TransactionDoc.from_bma_history(history_data["currency"], tx_data) if not self._transactions_processor.find_by_hash(connection.pubkey, tx_doc.sha_hash) \ and SimpleTransaction.is_simple(tx_doc): tx = parse_transaction_doc(tx_doc, connection.pubkey, tx_data["block_number"], tx_data["time"], txid) if tx: new_transfers[connection].append(tx) self._transactions_processor.commit(tx) else: logging.debug("Error during transfer parsing") return transfers_changed, new_transfers
def get_transaction_document(current_block: dict, source: dict, from_pubkey: str, to_pubkey: str) -> Transaction: """ Return a Transaction document :param current_block: Current block infos :param source: Source to send :param from_pubkey: Public key of the issuer :param to_pubkey: Public key of the receiver :return: Transaction """ # list of inputs (sources) inputs = [ InputSource( amount=source["amount"], base=source["base"], source=source["type"], origin_id=source["identifier"], index=source["noffset"], ) ] # list of issuers of the inputs issuers = [from_pubkey] # list of unlocks of the inputs unlocks = [ Unlock( # inputs[index] index=0, # unlock inputs[index] if signatures[0] is from public key of issuers[0] parameters=[SIGParameter(0)], ) ] # lists of outputs outputs = [ OutputSource( amount=source["amount"], base=source["base"], condition="SIG({0})".format(to_pubkey), ) ] transaction = Transaction( version=TRANSACTION_VERSION, currency=current_block["currency"], blockstamp=BlockUID(current_block["number"], current_block["hash"]), locktime=0, issuers=issuers, inputs=inputs, unlocks=unlocks, outputs=outputs, comment="", signatures=[], ) return transaction
async def parse_dividends_history(self, connections, blocks, transactions): """ Request transactions from the network to initialize data for a given pubkey :param List[sakia.data.entities.Connection] connections: the list of connections found by tx parsing :param List[duniterpy.documents.Block] blocks: the list of transactions found by tx parsing :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx parsing """ min_block_number = blocks[0].number max_block_number = blocks[-1].number dividends = {} for connection in connections: dividends[connection] = [] history_data = await self._bma_connector.get( self.currency, bma.ud.history, req_args={'pubkey': connection.pubkey}) block_numbers = [] for ud_data in history_data["history"]["history"]: dividend = Dividend(currency=self.currency, pubkey=connection.pubkey, block_number=ud_data["block_number"], timestamp=ud_data["time"], amount=ud_data["amount"], base=ud_data["base"]) if max_block_number >= dividend.block_number >= min_block_number: self._logger.debug("Dividend of block {0}".format( dividend.block_number)) block_numbers.append(dividend.block_number) if self._dividends_processor.commit(dividend): dividends[connection].append(dividend) for tx in transactions[connection]: txdoc = TransactionDoc.from_signed_raw(tx.raw) for input in txdoc.inputs: # For each dividends inputs, if it is consumed (not present in ud history) if input.source == "D" and input.origin_id == connection.pubkey and input.index not in block_numbers: try: # we try to get the block of the dividend block = next( (b for b in blocks if b.number == input.index)) except StopIteration: block_data = await self._bma_connector.get( self.currency, bma.blockchain.block, req_args={'number': input.index}) block = Block.from_signed_raw( block_data["raw"] + block_data["signature"] + "\n") dividend = Dividend(currency=self.currency, pubkey=connection.pubkey, block_number=input.index, timestamp=block.mediantime, amount=block.ud, base=block.unit_base) self._logger.debug("Dividend of block {0}".format( dividend.block_number)) if self._dividends_processor.commit(dividend): dividends[connection].append(dividend) return dividends
async def process(self, request): data = await request.post() if self.reject_next_post: self.reject_next_post = False return {'ucode': errors.UNHANDLED, 'message': "Rejected"}, 400 transaction = Transaction.from_signed_raw(data["transaction"]) self.forge.pool.append(transaction) return {}, 200
async def initialize_dividends(self, connection, transactions, log_stream, progress): """ Request transactions from the network to initialize data for a given pubkey :param sakia.data.entities.Connection connection: :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx processor :param function log_stream: :param function progress: progress callback """ blockchain = self._blockchain_repo.get_one( currency=connection.currency) avg_blocks_per_month = int(30 * 24 * 3600 / blockchain.parameters.avg_gen_time) start = blockchain.current_buid.number - avg_blocks_per_month history_data = await self._bma_connector.get( connection.currency, bma.ud.history, req_args={'pubkey': connection.pubkey}) block_numbers = [] dividends = [] for ud_data in history_data["history"]["history"]: if ud_data["block_number"] > start: dividend = Dividend(currency=connection.currency, pubkey=connection.pubkey, block_number=ud_data["block_number"], timestamp=ud_data["time"], amount=ud_data["amount"], base=ud_data["base"]) log_stream("Dividend of block {0}".format( dividend.block_number)) block_numbers.append(dividend.block_number) try: dividends.append(dividend) self._repo.insert(dividend) except sqlite3.IntegrityError: log_stream("Dividend already registered in database") for tx in transactions: txdoc = Transaction.from_signed_raw(tx.raw) for input in txdoc.inputs: if input.source == "D" and input.origin_id == connection.pubkey \ and input.index not in block_numbers and input.index > start: diff_blocks = blockchain.current_buid.number - input.index ud_mediantime = blockchain.median_time - diff_blocks * blockchain.parameters.avg_gen_time dividend = Dividend(currency=connection.currency, pubkey=connection.pubkey, block_number=input.index, timestamp=ud_mediantime, amount=input.amount, base=input.base) log_stream("Dividend of block {0}".format( dividend.block_number)) try: dividends.append(dividend) self._repo.insert(dividend) except sqlite3.IntegrityError: log_stream("Dividend already registered in database") return dividends
async def initialize_dividends(self, connection, transactions, log_stream): """ Request transactions from the network to initialize data for a given pubkey :param sakia.data.entities.Connection connection: :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx processor :param function log_stream: """ history_data = await self._bma_connector.get( connection.currency, bma.ud.history, req_args={'pubkey': connection.pubkey}) log_stream("Found {0} available dividends".format( len(history_data["history"]["history"]))) block_numbers = [] dividends = [] for ud_data in history_data["history"]["history"]: dividend = Dividend(currency=connection.currency, pubkey=connection.pubkey, block_number=ud_data["block_number"], timestamp=ud_data["time"], amount=ud_data["amount"], base=ud_data["base"]) log_stream("Dividend of block {0}".format(dividend.block_number)) block_numbers.append(dividend.block_number) try: dividends.append(dividend) self._repo.insert(dividend) except sqlite3.IntegrityError: log_stream("Dividend already registered in database") for tx in transactions: txdoc = Transaction.from_signed_raw(tx.raw) for input in txdoc.inputs: if input.source == "D" and input.origin_id == connection.pubkey and input.index not in block_numbers: block = await self._bma_connector.get( connection.currency, bma.blockchain.block, req_args={'number': input.index}) await asyncio.sleep(0.5) dividend = Dividend(currency=connection.currency, pubkey=connection.pubkey, block_number=input.index, timestamp=block["medianTime"], amount=block["dividend"], base=block["unitbase"]) log_stream("Dividend of block {0}".format( dividend.block_number)) try: dividends.append(dividend) self._repo.insert(dividend) except sqlite3.IntegrityError: log_stream("Dividend already registered in database") return dividends
def get_transaction_document(current_block, source, from_pubkey, to_pubkey): """ Return a Transaction document :param dict current_block: Current block infos :param dict source: Source to send :param str from_pubkey: Public key of the issuer :param str to_pubkey: Public key of the receiver :return: Transaction """ # list of inputs (sources) inputs = [ InputSource(amount=source['amount'], base=source['base'], source=source['type'], origin_id=source['identifier'], index=source['noffset']) ] # list of issuers of the inputs issuers = [from_pubkey] # list of unlocks of the inputs unlocks = [ Unlock( # inputs[index] index=0, # unlock inputs[index] if signatures[0] is from public key of issuers[0] parameters=[SIGParameter(0)]) ] # lists of outputs outputs = [ OutputSource( amount=source['amount'], base=source['base'], # only the receiver of the output can use it as input in another transaction conditions=Condition.token(SIG.token(to_pubkey))) ] transaction = Transaction(version=TRANSACTION_VERSION, currency=current_block['currency'], blockstamp=BlockUID(current_block['number'], current_block['hash']), locktime=0, issuers=issuers, inputs=inputs, unlocks=unlocks, outputs=outputs, comment='', signatures=None) return transaction
async def generate_transaction_document( issuers, tx_amounts, listinput_and_amount, outputAddresses, Comment="", OutputbackChange=None, ): listinput = listinput_and_amount[0] totalAmountInput = listinput_and_amount[1] total_tx_amount = sum(tx_amounts) head_block = await HeadBlock().head_block currency_name = head_block["currency"] blockstamp_current = BlockUID(head_block["number"], head_block["hash"]) curentUnitBase = head_block["unitbase"] if not OutputbackChange: OutputbackChange = issuers # if it's not a foreign exchange transaction, we remove units after 2 digits after the decimal point. if issuers not in outputAddresses: total_tx_amount = (total_tx_amount // 10**curentUnitBase) * 10**curentUnitBase # Generate output ################ listoutput = [] for tx_amount, outputAddress in zip(tx_amounts, outputAddresses): generate_output(listoutput, curentUnitBase, tx_amount, outputAddress) # Outputs to himself rest = totalAmountInput - total_tx_amount generate_output(listoutput, curentUnitBase, rest, OutputbackChange) # Unlocks unlocks = generate_unlocks(listinput) # Generate transaction document ############################## return Transaction( version=10, currency=currency_name, blockstamp=blockstamp_current, locktime=0, issuers=[issuers], inputs=listinput, unlocks=unlocks, outputs=listoutput, comment=Comment, signatures=[], )
async def initialize_dividends(self, connection, transactions, log_stream, progress): """ Request transactions from the network to initialize data for a given pubkey :param sakia.data.entities.Connection connection: :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx processor :param function log_stream: :param function progress: progress callback """ blockchain = self._blockchain_repo.get_one(currency=connection.currency) avg_blocks_per_month = int(30 * 24 * 3600 / blockchain.parameters.avg_gen_time) start = blockchain.current_buid.number - avg_blocks_per_month history_data = await self._bma_connector.get(connection.currency, bma.ud.history, req_args={'pubkey': connection.pubkey}) block_numbers = [] dividends = [] for ud_data in history_data["history"]["history"]: if ud_data["block_number"] > start: dividend = Dividend(currency=connection.currency, pubkey=connection.pubkey, block_number=ud_data["block_number"], timestamp=ud_data["time"], amount=ud_data["amount"], base=ud_data["base"]) log_stream("Dividend of block {0}".format(dividend.block_number)) block_numbers.append(dividend.block_number) try: dividends.append(dividend) self._repo.insert(dividend) except sqlite3.IntegrityError: log_stream("Dividend already registered in database") for tx in transactions: txdoc = Transaction.from_signed_raw(tx.raw) for input in txdoc.inputs: if input.source == "D" and input.origin_id == connection.pubkey \ and input.index not in block_numbers and input.index > start: diff_blocks = blockchain.current_buid.number - input.index ud_mediantime = blockchain.median_time - diff_blocks*blockchain.parameters.avg_gen_time dividend = Dividend(currency=connection.currency, pubkey=connection.pubkey, block_number=input.index, timestamp=ud_mediantime, amount=input.amount, base=input.base) log_stream("Dividend of block {0}".format(dividend.block_number)) try: dividends.append(dividend) self._repo.insert(dividend) except sqlite3.IntegrityError: log_stream("Dividend already registered in database") return dividends
def parse_transaction_inputs(self, pubkey, transaction): """ Parse a transaction :param sakia.data.entities.Transaction transaction: """ txdoc = TransactionDoc.from_signed_raw(transaction.raw) for index, input in enumerate(txdoc.inputs): source = Source(currency=self.currency, pubkey=txdoc.issuers[0], identifier=input.origin_id, type=input.source, noffset=input.index, amount=input.amount, base=input.base) if source.pubkey == pubkey: self._sources_processor.drop(source)
def parse_transaction_outputs(self, pubkey, transaction): """ Parse a transaction :param sakia.data.entities.Transaction transaction: """ txdoc = TransactionDoc.from_signed_raw(transaction.raw) for offset, output in enumerate(txdoc.outputs): if output.conditions.left.pubkey == pubkey: source = Source(currency=self.currency, pubkey=pubkey, identifier=txdoc.sha_hash, type='T', noffset=offset, amount=output.amount, base=output.base) self._sources_processor.insert(source)
async def initialize_transactions(self, connection, log_stream, progress): """ Request transactions from the network to initialize data for a given pubkey :param sakia.data.entities.Connection connection: :param function log_stream: :param function progress: progress callback """ blockchain = self._blockchain_repo.get_one( currency=connection.currency) avg_blocks_per_month = int(30 * 24 * 3600 / blockchain.parameters.avg_gen_time) start = blockchain.current_buid.number - avg_blocks_per_month end = blockchain.current_buid.number history_data = await self._bma_connector.get(connection.currency, bma.tx.blocks, req_args={ 'pubkey': connection.pubkey, 'start': start, 'end': end }) txid = 0 nb_tx = len(history_data["history"]["sent"]) + len( history_data["history"]["received"]) log_stream("Found {0} transactions".format(nb_tx)) transactions = [] for sent_data in history_data["history"]["sent"] + history_data[ "history"]["received"]: sent = TransactionDoc.from_bma_history(history_data["currency"], sent_data) log_stream("{0}/{1} transactions".format(txid, nb_tx)) progress(1 / nb_tx) try: tx = parse_transaction_doc(sent, connection.pubkey, sent_data["block_number"], sent_data["time"], txid) if tx: transactions.append(tx) self._repo.insert(tx) else: log_stream("ERROR : Could not parse transaction") except sqlite3.IntegrityError: log_stream("Transaction already registered in database") await asyncio.sleep(0) txid += 1 return transactions
async def parse_transactions_history(self, connections, start, end): """ Request transactions from the network to initialize data for a given pubkey :param List[sakia.data.entities.Connection] connections: the list of connections found by tx parsing :param int start: the first block :param int end: the last block """ transfers_changed = [] new_transfers = {} for connection in connections: txid = 0 new_transfers[connection] = [] history_data = await self._bma_connector.get(self.currency, bma.tx.blocks, req_args={ 'pubkey': connection.pubkey, 'start': start, 'end': end }) for tx_data in history_data["history"]["sent"]: for tx in [ t for t in self._transactions_processor.awaiting( self.currency) ]: if self._transactions_processor.run_state_transitions( tx, tx_data["hash"], tx_data["block_number"]): transfers_changed.append(tx) self._logger.debug( "New transaction validated : {0}".format( tx.sha_hash)) for tx_data in history_data["history"]["received"]: tx_doc = TransactionDoc.from_bma_history( history_data["currency"], tx_data) if not self._transactions_processor.find_by_hash(connection.pubkey, tx_doc.sha_hash) \ and SimpleTransaction.is_simple(tx_doc): tx = parse_transaction_doc(tx_doc, connection.pubkey, tx_data["block_number"], tx_data["time"], txid) if tx: new_transfers[connection].append(tx) self._transactions_processor.commit(tx) else: logging.debug("Error during transfer parsing") return transfers_changed, new_transfers
async def parse_dividends_history(self, connections, start, end, transactions): """ Request transactions from the network to initialize data for a given pubkey :param List[sakia.data.entities.Connection] connections: the list of connections found by tx parsing :param List[duniterpy.documents.Block] blocks: the list of transactions found by tx parsing :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx parsing """ dividends = {} for connection in connections: dividends[connection] = [] history_data = await self._bma_connector.get(self.currency, bma.ud.history, req_args={'pubkey': connection.pubkey}) block_numbers = [] for ud_data in history_data["history"]["history"]: dividend = Dividend(currency=self.currency, pubkey=connection.pubkey, block_number=ud_data["block_number"], timestamp=ud_data["time"], amount=ud_data["amount"], base=ud_data["base"]) if start <= dividend.block_number <= end: self._logger.debug("Dividend of block {0}".format(dividend.block_number)) block_numbers.append(dividend.block_number) if self._dividends_processor.commit(dividend): dividends[connection].append(dividend) for tx in transactions[connection]: txdoc = TransactionDoc.from_signed_raw(tx.raw) for input in txdoc.inputs: # For each dividends inputs, if it is consumed (not present in ud history) if input.source == "D" and input.origin_id == connection.pubkey and input.index not in block_numbers: block_data = await self._bma_connector.get(self.currency, bma.blockchain.block, req_args={'number': input.index}) block = Block.from_signed_raw(block_data["raw"] + block_data["signature"] + "\n") dividend = Dividend(currency=self.currency, pubkey=connection.pubkey, block_number=input.index, timestamp=block.mediantime, amount=block.ud, base=block.unit_base) self._logger.debug("Dividend of block {0}".format(dividend.block_number)) if self._dividends_processor.commit(dividend): dividends[connection].append(dividend) return dividends
async def initialize_transactions(self, connection, log_stream, progress): """ Request transactions from the network to initialize data for a given pubkey :param sakia.data.entities.Connection connection: :param function log_stream: :param function progress: progress callback """ blockchain = self._blockchain_repo.get_one(currency=connection.currency) avg_blocks_per_month = int(30 * 24 * 3600 / blockchain.parameters.avg_gen_time) start = blockchain.current_buid.number - avg_blocks_per_month end = blockchain.current_buid.number history_data = await self._bma_connector.get(connection.currency, bma.tx.blocks, req_args={'pubkey': connection.pubkey, 'start': start, 'end': end}) txid = 0 nb_tx = len(history_data["history"]["sent"]) + len(history_data["history"]["received"]) log_stream("Found {0} transactions".format(nb_tx)) transactions = [] for sent_data in history_data["history"]["sent"] + history_data["history"]["received"]: sent = TransactionDoc.from_bma_history(history_data["currency"], sent_data) log_stream("{0}/{1} transactions".format(txid, nb_tx)) progress(1 / nb_tx) try: tx = parse_transaction_doc(sent, connection.pubkey, sent_data["block_number"], sent_data["time"], txid) if tx: transactions.append(tx) self._repo.insert(tx) else: log_stream("ERROR : Could not parse transaction") except sqlite3.IntegrityError: log_stream("Transaction already registered in database") await asyncio.sleep(0) txid += 1 return transactions
def txdoc(self): """ :rtype: duniterpy.documents.Transaction """ return TransactionDoc.from_signed_raw(self.raw)