def get_digest_result(self): """Retrieving the digest of a particular ledger.""" try: data = get_requested_data() digest = get_digest_result(data["ledger_name"]) return digest except Exception as e: logger.exception('Unable to get a ledger digest!') return fail_response('Unable to delete the ledger. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def up(self): try: self.create_ledger() if self.table_exist(): logger.info('Table already exist!') return True self.create_table() self.create_table_indexes(['service_id', 'document_id', 'document_hash', 'field_list', 'op_mode']) except Exception as e: logger.exception('Migration UP failed! {}.'.format(e))
def ledger_exist(self): """ Returns information about a ledger, including its state and when it was created. """ try: logger.info("Let's describe ledger...{}".format(self.ledger_name)) ledger = qldb.client().describe_ledger(Name=self.ledger_name) logger.info('Success. describe ledger...{}.'.format(ledger)) return ledger except Exception as e: logger.exception('Unable to list ledgers!') return None
def describe_ledger(ledger_name): """ Returns information about a ledger, including its state and when it was created. """ try: logger.info("Let's describe ledger...{}".format(ledger_name)) ledger = qldb.client().describe_ledger(Name=ledger_name) logger.info('Success. describe ledger...{}.'.format(ledger)) return ledger except Exception as e: logger.exception('Unable to list ledgers!') return fail_response('Unable to list ledgers!. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def create_ledger(ledger_name): """Create a ledger and wait for it to be active.""" try: DdlServices.do_create_ledger(ledger_name) wait_for_active(ledger_name) DdlServices.reset_ledgers() return success_response('Ledger is active and ready to use.', HTTPStatus.CREATED) except Exception as e: logger.exception('Unable to create the ledger!') return fail_response( 'Unable to create the ledger. error: {}. Please try again.'. format(e), HTTPStatus.UNPROCESSABLE_ENTITY)
def delete_ledger(ledger_name): """Delete a ledger.""" try: set_deletion_protection(ledger_name, False) DdlServices.do_delete_ledger(ledger_name) wait_for_deleted(ledger_name) DdlServices.reset_ledgers() return success_response('The ledger is successfully deleted.', HTTPStatus.ACCEPTED) except Exception as e: logger.exception('Unable to delete the ledger.') return fail_response( 'Unable to delete the ledger. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def get_table_data(ledger_name, table_name, where='1=1', limit=10, offset=0): """ Scan for all the documents in a table.""" try: with qldb.session(ledger_name) as session: # Scan all the tables and print their documents. tables = session.list_tables() for table in tables: cursor = session.execute_lambda( lambda executor: DmlServices.scan_table(executor, table_name, where, limit, offset), retry_indicator=lambda retry_attempt: logger.info('Retrying due to OCC conflict...')) logger.info('Scan successful!') return parse_result(cursor) except Exception as e: logger.exception('Unable to scan tables. {}'.format(e))
def table_exist(self): """ Connect to a session for a given ledger using default settings. """ try: qldb_session = qldb.session(self.ledger_name) logger.info('Listing table names ') tables = qldb_session.list_tables() _tables = [] for table in tables: _tables.append(table) return self.hash_table_name in _tables except Exception as e: logger.exception('Unable to create session.') return False
def get_ledger_list(): """ List all ledgers. :rtype: list :return: List of ledgers. """ try: logger.info("Let's list all the ledgers...") ledgers = DdlServices.ledger_list() logger.info('Success. List of ledgers: {}.'.format(ledgers)) return ledgers except Exception as e: logger.exception('Unable to list ledgers!') return fail_response('Unable to list ledgers!. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def drop_table(ledger_name=None, table_name=None): """Create a Table""" try: with qldb.session(ledger_name) as session: session.execute_lambda( lambda x: DdlServices.do_drop_table(x, table_name), lambda retry_attempt: logger.info( 'Retrying due to OCC conflict...')) logger.info('Table dropped successfully.') return success_response('Table dropped successfully.', HTTPStatus.CREATED) except Exception: logger.exception('Error creating table.') return fail_response( 'Unable to create the table. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def list_tables(ledger_name): """ Connect to a session for a given ledger using default settings. """ try: qldb_session = qldb.session(ledger_name) logger.info('Listing table names ') tables = qldb_session.list_tables() _tables = [] for table in tables: _tables.append(table) return _tables except Exception as e: logger.exception('Unable to create session.') return fail_response( 'Unable to connect ledgers!. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def insert_documents(ledger_name, table_name, documents): """ Insert documents into a table in a QLDB ledger. """ try: with qldb.session(ledger_name) as session: # An INSERT statement creates the initial revision of a document with a version number of zero. # QLDB also assigns a unique document identifier in GUID format as part of the metadata. session.execute_lambda( lambda executor: DmlServices.do_insert_documents(executor, table_name, documents), lambda retry_attempt: logger.info('Retrying due to OCC conflict...')) logger.info('Documents inserted successfully!') return success_response('Ledger is active and ready to use.', HTTPStatus.CREATED) except Exception as e: logger.exception('Error inserting or updating documents.') return fail_response('Error inserting or updating documents. error: {}. Please try again.'.format(e), HTTPStatus.UNPROCESSABLE_ENTITY)
def create_export_and_wait_for_completion(name, bucket, prefix, encryption_config, role_arn=None): """ Request QLDB to export the contents of the journal for the given time period and S3 configuration. Before calling this function the S3 bucket should be created, see :py:class:`hash_chain.ledger.export_journal.create_s3_bucket_if_not_exists` :type name: str :param name: Name of the ledger to create a journal export for. :type bucket: str :param bucket: S3 bucket to write the data to. :type prefix: str :param prefix: S3 prefix to be prefixed to the files being written. :type encryption_config: dict :param encryption_config: Encryption configuration for S3. :type role_arn: str :param role_arn: The IAM role ARN to be used when exporting the journal. :rtype: dict :return: The result of the request. """ if role_arn is None: role_arn = create_export_role(EXPORT_ROLE_NAME, encryption_config.get('KmsKeyArn'), ROLE_POLICY_NAME, bucket) try: start_time = datetime.utcnow() - timedelta( minutes=JOURNAL_EXPORT_TIME_WINDOW_MINUTES) end_time = datetime.utcnow() result = create_export(name, start_time, end_time, bucket, prefix, encryption_config, role_arn) wait_for_export_to_complete(config.LEDGER_NAME, result.get('ExportId')) logger.info('JournalS3Export for exportId {} is completed.'.format( result.get('ExportId'))) return result except Exception as e: logger.exception('Unable to create an export!') raise e
def create_table_index(ledger_name=None, table_name=None, index_attribute=None): """Create index on tables in a particular ledger.""" logger.info('Creating index on all tables in a single transaction...') try: with qldb.session(ledger_name) as session: session.execute_lambda( lambda x: DdlServices.do_create_table_index( x, table_name, index_attribute), lambda retry_attempt: logger.info('Retrying due to OCC conflict...')) logger.info('Index created successfully.') return success_response('Index created successfully.', HTTPStatus.CREATED) except Exception: logger.exception('Unable to create index.') return fail_response( 'Unable to create the index. Please try again.', HTTPStatus.UNPROCESSABLE_ENTITY)
def verify_block(self): """ Get a journal block from a QLDB ledger. After getting the block, we get the digest of the ledger and validate the proof returned in the getBlock response. """ try: data = get_requested_data() with qldb.session(data["ledger_name"]) as session: cursor = session.execute_lambda(lambda executor: query_revision_history(executor, data["table_name"], data["condition_str"], data["condition_value"]), lambda retry_indicator: logger.info('Retrying due to OCC conflict...')) row = next(cursor) block_address = row.get('blockAddress') verify_block(data["ledger_name"], block_address) except Exception: logger.exception('Unable to query vehicle registration by Vin.')
def down(self): try: self.drop_table() self.drop_ledger() except Exception as e: logger.exception('Migration DOWN failed! {}.'.format(e))
def verify_block(ledger_name, block_address): """ Verify block by validating the proof returned in the getBlock response. :type ledger_name: str :param ledger_name: The ledger to get digest from. :type block_address: str/:py:class:`amazon.ion.simple_types.IonPyDict` :param block_address: The address of the block to verify. :raises AssertionError: When verification failed. """ logger.info( "Let's verify blocks for ledger with name={}.".format(ledger_name)) try: logger.info("First, let's get a digest.") digest_result = get_digest_result(ledger_name) digest_tip_address = digest_result.get('DigestTipAddress') digest_bytes = digest_result.get('Digest') logger.info( 'Got a ledger digest. Digest end address={}, digest={}'.format( value_holder_to_string(digest_tip_address.get('IonText')), to_base_64(digest_bytes))) get_block_result = get_block_with_proof( ledger_name, block_address_to_dictionary(block_address), digest_tip_address) block = get_block_result.get('Block') block_hash = parse_block(block) verified = verify_document(block_hash, digest_bytes, get_block_result.get('Proof')) if not verified: raise AssertionError('Block is not verified!') else: logger.info('Success! The block is verified.') altered_digest = flip_random_bit(digest_bytes) logger.info( "Let's try flipping one bit in the digest and assert that the block is NOT verified. " "The altered digest is: {}".format(to_base_64(altered_digest))) verified = verify_document(block_hash, altered_digest, get_block_result.get('Proof')) if verified: raise AssertionError( 'Expected block to not be verified against altered digest.') else: logger.info( 'Success! As expected flipping a bit in the digest causes verification to fail.' ) altered_block_hash = flip_random_bit(block_hash) logger.info( "Let's try flipping one bit in the block's hash and assert that the block is NOT verified. " "The altered block hash is: {}.".format( to_base_64(altered_block_hash))) verified = verify_document(altered_block_hash, digest_bytes, get_block_result.get('Proof')) if verified: raise AssertionError( 'Expected altered block hash to not be verified against digest.' ) else: logger.info( 'Success! As expected flipping a bit in the block hash causes verification to fail.' ) except Exception as e: logger.exception( 'Failed to verify blocks in the ledger with name={}.'.format( ledger_name)) raise e
if len(journal_blocks) == 0: return reduce(compare_journal_blocks, journal_blocks) if __name__ == '__main__': """ Validate the hash chain of a QLDB ledger by stepping through its S3 export. This code accepts an exportID as an argument, if exportID is passed the code will use that or request QLDB to generate a new export to perform QLDB hash chain validation. """ s3_client = client('s3') try: if len(argv) == 2: export_id = argv[1] logger.info('Validating qldb hash chain for ExportId: {}.'.format( export_id)) else: logger.info('Requesting qldb to create an export.') export_id = create_journal_export() journal_export = describe_journal_export( config.LEDGER_NAME, export_id).get('ExportDescription') journal_blocks = read_export(journal_export, s3_client) verify(journal_blocks) except Exception: logger.exception('Unable to perform hash chain verification.')