async def handle_new_blocks(self, currency, network_blockstamp): """ Initialize blockchain for a given currency if no source exists locally :param str currency: :param BlockUID network_blockstamp: the blockstamp of the network """ self._logger.debug("Requesting current block") try: current_block = await self._bma_connector.get( currency, bma.blockchain.block, req_args={'number': network_blockstamp.number}) signed_raw = "{0}{1}\n".format(current_block['raw'], current_block['signature']) block = Block.from_signed_raw(signed_raw) blockchain = self._repo.get_one(currency=currency) blockchain.current_buid = block.blockUID blockchain.median_time = block.mediantime blockchain.current_members_count = block.members_count if blockchain.last_ud_time + blockchain.parameters.dt <= blockchain.median_time: await self.refresh_dividend_data(currency, blockchain) self._repo.update(blockchain) except errors.DuniterError as e: if e.ucode != errors.NO_CURRENT_BLOCK: raise
async def send(self, txdoc, community): """ Send a transaction and update the transfer state to AWAITING if accepted. If the transaction was refused (return code != 200), state becomes REFUSED The txdoc is saved as the transfer txdoc. :param txdoc: A transaction duniterpy object :param community: The community target of the transaction """ self.sha_hash = txdoc.sha_hash responses = await community.bma_access.broadcast( bma.tx.Process, post_args={'transaction': txdoc.signed_raw()}) blockUID = community.network.current_blockUID block = await community.bma_access.future_request( bma.blockchain.Block, req_args={'number': blockUID.number}) signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) block_doc = Block.from_signed_raw(signed_raw) result = (False, "") for r in responses: if r.status == 200: result = (True, (await r.json())) elif not result[0]: result = (False, (await r.text())) else: await r.text() self.run_state_transitions(([r.status for r in responses], block_doc)) self.run_state_transitions(([r.status for r in responses], )) return result
async def copy_block_to_clipboard(self, number): clipboard = QApplication.clipboard() block = await self._community.get_block(number) if block: block_doc = Block.from_signed_raw("{0}{1}\n".format( block['raw'], block['signature'])) clipboard.setText(block_doc.signed_raw())
def test_block_document(self): block_document = """Version: 10 Type: Block Currency: g1 Number: 15145 PoWMin: 80 Time: 1493684276 MedianTime: 1493681245 UnitBase: 0 Issuer: 6fFt4zdvtNyVcfJn7Y41mKLmMDizyK3nVeNW3qdDXzpc IssuersFrame: 106 IssuersFrameVar: 0 DifferentIssuersCount: 21 PreviousHash: 00000A0CE0AE54F3F6B63383F386067160C477B5338FB93AF3AF0776A959AA32 PreviousIssuer: D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx MembersCount: 98 Identities: Joiners: Actives: Leavers: Revoked: Excluded: Certifications: Transactions: InnerHash: AA01ABD5C6D3F99A189C0CF0E37768DA0F876526AF93FE150E92B135D4AD0D85 Nonce: 10300000099432 """ block_signature = "Uxa3L+/m/dWLex2xSh7Jv1beAn4f99BmoYAs7iX3Lr+t1l5jzJpd9m4iI1cHppIizCgbg6ztaiZedQ+Mp6KuDg==" block = Block.from_signed_raw(block_document + block_signature + "\n") verifying_key = VerifyingKey(block.issuer) self.assertTrue(verifying_key.verify_document(block))
async def send(self, txdoc, community): """ Send a transaction and update the transfer state to AWAITING if accepted. If the transaction was refused (return code != 200), state becomes REFUSED The txdoc is saved as the transfer txdoc. :param txdoc: A transaction duniterpy object :param community: The community target of the transaction """ self.sha_hash = txdoc.sha_hash responses = await community.bma_access.broadcast(bma.tx.Process, post_args={'transaction': txdoc.signed_raw()}) blockUID = community.network.current_blockUID block = await community.bma_access.future_request(bma.blockchain.Block, req_args={'number': blockUID.number}) signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) block_doc = Block.from_signed_raw(signed_raw) result = (False, "") for r in responses: if r.status == 200: result = (True, (await r.json())) elif not result[0]: result = (False, (await r.text())) else: await r.text() self.run_state_transitions(([r.status for r in responses], block_doc)) self.run_state_transitions(([r.status for r in responses], )) return result
async def _get_block_doc(self, community, number): """ Retrieve the current block document :param sakia.core.Community community: The community we look for a block :param int number: The block number to retrieve :return: the block doc or None if no block was found """ tries = 0 block_doc = None block = None while block is None and tries < 3: try: block = await community.bma_access.future_request( bma.blockchain.Block, req_args={'number': number}) signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) try: block_doc = Block.from_signed_raw(signed_raw) except TypeError: logging.debug("Error in {0}".format(number)) block = None tries += 1 except errors.DuniterError as e: if e.ucode == errors.BLOCK_NOT_FOUND: block = None tries += 1 return block_doc
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
def test_verify_block_signature(signature, block_raw): # Check with valid and non-valid signatures block invalid_signatures_blocks = [] block = Block.from_signed_raw(block_raw + signature + "\n") verify_block_signature(invalid_signatures_blocks, block) if block.number == G1_INVALID_BLOCK_SIG: assert invalid_signatures_blocks == [block.number] else: assert invalid_signatures_blocks == []
async def initialize_blockchain(self, currency): """ Initialize blockchain for a given currency if no source exists locally """ blockchain = self._repo.get_one(currency=currency) if not blockchain: blockchain = Blockchain(currency=currency) self._logger.debug("Requesting blockchain parameters") try: parameters = await self._bma_connector.get( currency, bma.blockchain.parameters) blockchain.parameters.ms_validity = parameters['msValidity'] blockchain.parameters.avg_gen_time = parameters['avgGenTime'] blockchain.parameters.c = parameters['c'] blockchain.parameters.dt = parameters['dt'] blockchain.parameters.dt_diff_eval = parameters['dtDiffEval'] blockchain.parameters.median_time_blocks = parameters[ 'medianTimeBlocks'] blockchain.parameters.percent_rot = parameters['percentRot'] blockchain.parameters.idty_window = parameters['idtyWindow'] blockchain.parameters.ms_window = parameters['msWindow'] blockchain.parameters.sig_window = parameters['sigWindow'] blockchain.parameters.sig_period = parameters['sigPeriod'] blockchain.parameters.sig_qty = parameters['sigQty'] blockchain.parameters.sig_stock = parameters['sigStock'] blockchain.parameters.sig_validity = parameters['sigValidity'] blockchain.parameters.sig_qty = parameters['sigQty'] blockchain.parameters.sig_period = parameters['sigPeriod'] blockchain.parameters.ud0 = parameters['ud0'] blockchain.parameters.ud_time_0 = parameters['udTime0'] blockchain.parameters.dt_reeval = parameters['dtReeval'] blockchain.parameters.ud_reeval_time_0 = parameters[ 'udReevalTime0'] blockchain.parameters.xpercent = parameters['xpercent'] except errors.DuniterError as e: raise self._logger.debug("Requesting current block") try: current_block = await self._bma_connector.get( currency, bma.blockchain.current) signed_raw = "{0}{1}\n".format(current_block['raw'], current_block['signature']) block = Block.from_signed_raw(signed_raw) blockchain.current_buid = block.blockUID blockchain.median_time = block.mediantime blockchain.current_members_count = block.members_count blockchain.current_mass = current_block['monetaryMass'] except errors.DuniterError as e: if e.ucode != errors.NO_CURRENT_BLOCK: raise await self.refresh_dividend_data(currency, blockchain) try: self._repo.insert(blockchain) except sqlite3.IntegrityError: self._repo.update(blockchain)
async def get_raw_document(self, community): """ Get the raw documents of this transfer """ block = await community.get_block(self.blockUID.number) if block: block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature'])) for tx in block_doc.transactions: if tx.sha_hash == self.sha_hash: return tx
async def get_raw_document(self, community): """ Get the raw documents of this transfer """ block = await community.get_block(self.blockUID.number) if block: block_doc = Block.from_signed_raw("{0}{1}\n".format( block['raw'], block['signature'])) for tx in block_doc.transactions: if tx.sha_hash == self.sha_hash: return tx
async def get_block(self, currency, number): """ Get block documen at a given number :param str currency: :param int number: :rtype: duniterpy.documents.Block """ block = await self._bma_connector.get(currency, bma.blockchain.block, req_args={'number': number}) if block: block_doc = Block.from_signed_raw("{0}{1}\n".format( block['raw'], block['signature'])) return block_doc
async def copy_membership_to_clipboard(self, identity): """ :param sakia.core.registry.Identity identity: :return: """ clipboard = QApplication.clipboard() try: membership = await identity.membership(self._community) if membership: block_number = membership['written'] block = await self._community.get_block(block_number) block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature'])) for ms_doc in block_doc.joiners: if ms_doc.issuer == identity.pubkey: clipboard.setText(ms_doc.signed_raw()) except MembershipNotFoundError: logging.debug("Could not find membership")
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 copy_membership_to_clipboard(self, identity): """ :param sakia.core.registry.Identity identity: :return: """ clipboard = QApplication.clipboard() try: membership = await identity.membership(self._community) if membership: block_number = membership['written'] block = await self._community.get_block(block_number) block_doc = Block.from_signed_raw("{0}{1}\n".format( block['raw'], block['signature'])) for ms_doc in block_doc.joiners: if ms_doc.issuer == identity.pubkey: clipboard.setText(ms_doc.signed_raw()) except MembershipNotFoundError: logging.debug("Could not find membership")
async def next_blocks(self, start, filter, currency): """ Get blocks from the network :param List[int] numbers: list of blocks numbers to get :return: the list of block documents :rtype: List[duniterpy.documents.Block] """ blocks = [] blocks_data = await self._bma_connector.get(currency, bma.blockchain.blocks, req_args={ 'count': 100, 'start': start }) for data in blocks_data: if data['number'] in filter or data['number'] == start + 99: blocks.append( Block.from_signed_raw(data["raw"] + data["signature"] + "\n")) return blocks
async def verify_blocks_signatures(from_block, to_block): client = Client(EndPoint().BMA_ENDPOINT) to_block = await check_passed_blocks_range(client, from_block, to_block) invalid_blocks_signatures = list() chunks_from = range(from_block, to_block + 1, BMA_MAX_BLOCKS_CHUNK_SIZE) with progressbar(chunks_from, label="Processing blocks verification") as bar: for chunk_from in bar: chunk_size = get_chunk_size(from_block, to_block, chunks_from, chunk_from) logging.info("Processing chunk from block {} to {}".format( chunk_from, chunk_from + chunk_size)) chunk = await get_chunk(client, chunk_size, chunk_from) for block in chunk: block = Block.from_signed_raw(block["raw"] + block["signature"] + "\n") verify_block_signature(invalid_blocks_signatures, block) await client.close() display_result(from_block, to_block, invalid_blocks_signatures)
async def send_selfcert(self, password, community): """ Send our self certification to a target community :param str password: The account SigningKey password :param community: The community target of the self certification """ try: block_data = await community.bma_access.simple_request(bma.blockchain.Current) signed_raw = "{0}{1}\n".format(block_data['raw'], block_data['signature']) block_uid = Block.from_signed_raw(signed_raw).blockUID except errors.DuniterError as e: if e.ucode == errors.NO_CURRENT_BLOCK: block_uid = BlockUID.empty() else: raise selfcert = SelfCertification(PROTOCOL_VERSION, community.currency, self.pubkey, self.name, block_uid, None) key = SigningKey(self.salt, password) selfcert.sign([key]) logging.debug("Key publish : {0}".format(selfcert.signed_raw())) responses = await community.bma_access.broadcast(bma.wot.Add, {}, {'identity': selfcert.signed_raw()}) result = (False, "") for r in responses: if r.status == 200: result = (True, (await r.json())) elif not result[0]: result = (False, (await r.text())) else: await r.release() if result[0]: (await self.identity(community)).sigdate = block_uid return result
async def initialize_blockchain(self, currency, log_stream): """ Initialize blockchain for a given currency if no source exists locally """ blockchain = self._repo.get_one(currency=currency) if not blockchain: blockchain = Blockchain(currency=currency) log_stream("Requesting blockchain parameters") try: parameters = await self._bma_connector.get( currency, bma.blockchain.parameters) blockchain.parameters.ms_validity = parameters['msValidity'] blockchain.parameters.avg_gen_time = parameters['avgGenTime'] blockchain.parameters.c = parameters['c'] blockchain.parameters.dt = parameters['dt'] blockchain.parameters.dt_diff_eval = parameters['dtDiffEval'] blockchain.parameters.median_time_blocks = parameters[ 'medianTimeBlocks'] blockchain.parameters.percent_rot = parameters['percentRot'] blockchain.parameters.idty_window = parameters['idtyWindow'] blockchain.parameters.ms_window = parameters['msWindow'] blockchain.parameters.sig_window = parameters['sigWindow'] blockchain.parameters.sig_period = parameters['sigPeriod'] blockchain.parameters.sig_qty = parameters['sigQty'] blockchain.parameters.sig_stock = parameters['sigStock'] blockchain.parameters.sig_validity = parameters['sigValidity'] blockchain.parameters.sig_qty = parameters['sigQty'] blockchain.parameters.sig_period = parameters['sigPeriod'] blockchain.parameters.ud0 = parameters['ud0'] blockchain.parameters.dt_reeval = parameters['dtReeval'] blockchain.parameters.ud_reeval_time_0 = parameters[ 'udReevalTime0'] blockchain.parameters.xpercent = parameters['xpercent'] except errors.DuniterError as e: raise log_stream("Requesting current block") try: current_block = await self._bma_connector.get( currency, bma.blockchain.current) signed_raw = "{0}{1}\n".format(current_block['raw'], current_block['signature']) block = Block.from_signed_raw(signed_raw) blockchain.current_buid = block.blockUID blockchain.median_time = block.mediantime blockchain.current_members_count = block.members_count except errors.DuniterError as e: if e.ucode != errors.NO_CURRENT_BLOCK: raise log_stream("Requesting blocks with dividend") with_ud = await self._bma_connector.get(currency, bma.blockchain.ud) blocks_with_ud = with_ud['result']['blocks'] if len(blocks_with_ud) > 0: log_stream("Requesting last block with dividend") try: index = max(len(blocks_with_ud) - 1, 0) block_number = blocks_with_ud[index] block_with_ud = await self._bma_connector.get( currency, bma.blockchain.block, req_args={'number': block_number}) if block_with_ud: blockchain.last_members_count = block_with_ud[ 'membersCount'] blockchain.last_ud = block_with_ud['dividend'] blockchain.last_ud_base = block_with_ud['unitbase'] blockchain.last_ud_time = block_with_ud['medianTime'] blockchain.current_mass = block_with_ud['monetaryMass'] except errors.DuniterError as e: if e.ucode != errors.NO_CURRENT_BLOCK: raise log_stream("Requesting previous block with dividend") try: index = max(len(blocks_with_ud) - 2, 0) block_number = blocks_with_ud[index] block_with_ud = await self._bma_connector.get( currency, bma.blockchain.block, req_args={'number': block_number}) blockchain.previous_mass = block_with_ud['monetaryMass'] blockchain.previous_members_count = block_with_ud[ 'membersCount'] blockchain.previous_ud = block_with_ud['dividend'] blockchain.previous_ud_base = block_with_ud['unitbase'] blockchain.previous_ud_time = block_with_ud['medianTime'] except errors.DuniterError as e: if e.ucode != errors.NO_CURRENT_BLOCK: raise try: self._repo.insert(blockchain) except sqlite3.IntegrityError: self._repo.update(blockchain)
async def _refresh(self, community, block_number_from, block_to, received_list): """ Refresh last transactions :param sakia.core.Community community: The community :param list received_list: List of transactions received """ new_transfers = [] new_dividends = [] try: logging.debug("Refresh from : {0} to {1}".format( block_number_from, block_to['number'])) dividends = await self.request_dividends(community, block_number_from) with_tx_data = await community.bma_access.future_request( bma.blockchain.TX) blocks_with_tx = with_tx_data['result']['blocks'] while block_number_from <= block_to['number']: udid = 0 for d in [ ud for ud in dividends if ud['block_number'] == block_number_from ]: state = TransferState.VALIDATED if block_number_from + MAX_CONFIRMATIONS <= block_to['number'] \ else TransferState.VALIDATING if d['block_number'] not in [ ud['block_number'] for ud in self._dividends ]: d['id'] = udid d['state'] = state new_dividends.append(d) udid += 1 else: known_dividend = [ ud for ud in self._dividends if ud['block_number'] == d['block_number'] ][0] known_dividend['state'] = state # We parse only blocks with transactions if block_number_from in blocks_with_tx: transfers = await self._parse_block( community, block_number_from, received_list, udid + len(new_transfers)) new_transfers += transfers self.wallet.refresh_progressed.emit(block_number_from, block_to['number'], self.wallet.pubkey) block_number_from += 1 signed_raw = "{0}{1}\n".format(block_to['raw'], block_to['signature']) block_to = Block.from_signed_raw(signed_raw) for transfer in [ t for t in self._transfers + new_transfers if t.state == TransferState.VALIDATING ]: transfer.run_state_transitions( (False, block_to, MAX_CONFIRMATIONS)) # We check if latest parsed block_number is a new high number if block_number_from > self.latest_block: self.available_sources = await self.wallet.sources(community) if self._stop_coroutines: return self.latest_block = block_number_from parameters = await community.parameters() for transfer in [ t for t in self._transfers if t.state == TransferState.AWAITING ]: transfer.run_state_transitions( (False, block_to, parameters['avgGenTime'], parameters['medianTimeBlocks'])) except (MalformedDocumentError, NoPeerAvailable) as e: logging.debug(str(e)) self.wallet.refresh_finished.emit([]) return self._transfers = self._transfers + new_transfers self._dividends = self._dividends + new_dividends self.wallet.refresh_finished.emit(received_list)
async def copy_block_to_clipboard(self, number): clipboard = QApplication.clipboard() block = await self._community.get_block(number) if block: block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature'])) clipboard.setText(block_doc.signed_raw())