async def send_schema(self, schema_data_json: str) -> str: """ Send schema to ledger, then retrieve it as written to the ledger and return it. If schema already exists on ledger, log error and return schema. :param schema_data_json: schema data json with name, version, attribute names; e.g., :: { 'name': 'my-schema', 'version': '1.234', 'attr_names': ['favourite_drink', 'height', 'last_visit_date'] } :return: schema json as written to ledger (or existed a priori) """ LOGGER.debug('Origin.send_schema >>> schema_data_json: %s', schema_data_json) schema_data = json.loads(schema_data_json) s_key = schema_key( schema_id(self.did, schema_data['name'], schema_data['version'])) with SCHEMA_CACHE.lock: try: rv_json = await self.get_schema(s_key) LOGGER.error( 'Schema %s version %s already exists on ledger for origin-did %s: not sending', schema_data['name'], schema_data['version'], self.did) except AbsentSchema: # OK - about to create and send it (_, schema_json) = await anoncreds.issuer_create_schema( self.did, schema_data['name'], schema_data['version'], json.dumps(schema_data['attr_names'])) req_json = await ledger.build_schema_request( self.did, schema_json) resp_json = await self._sign_submit(req_json) resp = json.loads(resp_json) resp_result_txn = resp['result']['txn'] rv_json = await self.get_schema( schema_key( schema_id(resp_result_txn['metadata']['from'], resp_result_txn['data']['data']['name'], resp_result_txn['data']['data']['version'])) ) # add to cache en passant LOGGER.debug('Origin.send_schema <<< %s', rv_json) return rv_json
async def test_schema_cache(): N = 32 s_key = [] schema = [] for i in range(N): s_key.append( SchemaKey('did.{}'.format(i), 'schema-{}'.format(i // 5), '{}'.format(i % 5))) schema.append({ # 'id': schema_id(s_key[i].origin_did, s_key[i].name, s_key[i].version), 'id': schema_id(*s_key[i]), 'name': s_key[i].version, 'version': s_key[i].version, 'seqNo': i, 'attrNames': ['attr-{}-{}'.format(i, j) for j in range(N)], 'ver': '1.0' }) for i in range(N): if i % 2: SCHEMA_CACHE[s_key[i]] = schema[i] else: SCHEMA_CACHE[schema[i]['seqNo']] = schema[i] for i in range(N): assert SCHEMA_CACHE.contains(s_key[i]) assert SCHEMA_CACHE.contains(schema[i]['seqNo']) assert SCHEMA_CACHE[s_key[i]] == SCHEMA_CACHE[schema[i]['seqNo']] assert len(SCHEMA_CACHE.index()) == N assert not SCHEMA_CACHE.contains(-1) try: SCHEMA_CACHE[-1] except CacheIndex: pass # Exercise cache clearing and feeding cached = SCHEMA_CACHE.schemata() assert SCHEMA_CACHE.schemata() cached_json = json.dumps(cached) SCHEMA_CACHE.clear() assert not SCHEMA_CACHE.schemata() SCHEMA_CACHE.feed(json.loads(cached_json)) assert len(SCHEMA_CACHE.schemata()) == len(cached)
async def test_schema_cache(): N = 5 s_key = [] schema = [] for i in range(N): s_key.append( SchemaKey('did.{}'.format(i), 'schema-{}'.format(i // 5), '{}'.format(i % 5))) schema.append({ # 'id': schema_id(s_key[i].origin_did, s_key[i].name, s_key[i].version), 'id': schema_id(*s_key[i]), 'name': s_key[i].version, 'version': s_key[i].version, 'seqNo': i, 'attrNames': ['attr-{}-{}'.format(i, j) for j in range(N)], 'ver': '1.0' }) for i in range(N): if i % 2: SCHEMA_CACHE[s_key[i]] = schema[i] else: SCHEMA_CACHE[schema[i]['seqNo']] = schema[i] for i in range(N): assert SCHEMA_CACHE.contains(s_key[i]) assert SCHEMA_CACHE.contains(schema[i]['seqNo']) assert SCHEMA_CACHE[s_key[i]] == SCHEMA_CACHE[schema[i]['seqNo']] assert len(SCHEMA_CACHE.index()) == N assert not SCHEMA_CACHE.contains(-1) try: SCHEMA_CACHE[-1] except CacheIndex: pass
async def _resolve_schemas(self) -> bool: LOGGER.info('Resolving schemas for prover') await asyncio.sleep(5) synced = False while not synced: missing = set() for spec in self._request_specs.values(): for schema in spec['schemas']: if 'id' not in schema: s_key = schema['key'] if 'did' not in s_key: missing.add((s_key['name'], s_key['version'])) else: schema['id'] = schema_id(s_key['did'], s_key['name'], s_key['version']) if not missing: synced = True else: for s_key in missing: self.send('issuer-manager', None, ResolveSchemaRequest(*s_key)) await asyncio.sleep(1) return synced
async def _publish_schema(self, issuer: VonIssuer, schema: dict) -> None: """ Check the ledger for a specific schema and version, and publish it if not found. Also publish the related credential definition if not found Args: issuer: the initialized and opened issuer instance publishing the schema schema: a dict which will be updated with the published schema and credential def """ if not schema or "definition" not in schema: raise ValueError("Missing schema definition") definition = schema["definition"] if not schema.get("ledger"): LOGGER.info( "Checking for schema: %s (%s)", definition.name, definition.version, ) # Check if schema exists on ledger try: s_key = schema_key( schema_id(issuer.did, definition.name, definition.version) ) schema_json = await issuer.get_schema(s_key) ledger_schema = json.loads(schema_json) log_json("Schema found on ledger:", ledger_schema, LOGGER) except AbsentSchema: # If not found, send the schema to the ledger LOGGER.info( "Publishing schema: %s (%s)", definition.name, definition.version, ) schema_json = await issuer.send_schema( json.dumps( { "name": definition.name, "version": definition.version, "attr_names": definition.attr_names, } ) ) ledger_schema = json.loads(schema_json) if not ledger_schema or not ledger_schema.get("seqNo"): raise RuntimeError("Schema was not published to ledger") log_json("Published schema:", ledger_schema, LOGGER) schema["ledger"] = ledger_schema if not schema.get("credential_definition"): # Check if credential definition has been published LOGGER.info( "Checking for credential def: %s (%s)", definition.name, definition.version, ) try: cred_def_json = await issuer.get_cred_def( cred_def_id(issuer.did, schema["ledger"]["seqNo"]) ) cred_def = json.loads(cred_def_json) log_json("Credential def found on ledger:", cred_def, LOGGER) except AbsentCredDef: # If credential definition is not found then publish it LOGGER.info( "Publishing credential def: %s (%s)", definition.name, definition.version, ) cred_def_json = await issuer.send_cred_def( schema_json, revocation=False ) cred_def = json.loads(cred_def_json) log_json("Published credential def:", cred_def, LOGGER) schema["credential_definition"] = cred_def
async def get_schema(self, index: Union[SchemaKey, int, str]) -> str: """ Get schema from ledger by SchemaKey namedtuple (origin DID, name, version), sequence number, or schema identifier. Raise AbsentSchema for no such schema, logging any error condition and raising BadLedgerTxn on bad request. Retrieve the schema from the agent's schema cache if it has it; cache it en passant if it does not (and there is a corresponding schema on the ledger). :param index: schema key (origin DID, name, version), sequence number, or schema identifier :return: schema json, parsed from ledger """ LOGGER.debug('_BaseAgent.get_schema >>> index: %s', index) rv_json = json.dumps({}) with SCHEMA_CACHE.lock: if SCHEMA_CACHE.contains(index): LOGGER.info( '_BaseAgent.get_schema: got schema %s from schema cache', index) rv_json = SCHEMA_CACHE[index] LOGGER.debug('_BaseAgent.get_schema <<< %s', rv_json) return json.dumps(rv_json) if isinstance(index, SchemaKey) or (isinstance(index, str) and ':2:' in index): s_id = schema_id( *index) if isinstance(index, SchemaKey) else index s_key = schema_key(s_id) req_json = await ledger.build_get_schema_request( self.did, s_id) resp_json = await self._submit(req_json) resp = json.loads(resp_json) if not ('result' in resp and resp['result'].get( 'data', {}).get('attr_names', None)): LOGGER.debug( '_BaseAgent.get_schema: <!< no schema exists on %s', index) raise AbsentSchema('No schema exists on {}'.format(index)) try: (_, rv_json ) = await ledger.parse_get_schema_response(resp_json) except IndyError: # ledger replied, but there is no such schema LOGGER.debug( '_BaseAgent.get_schema: <!< no schema exists on %s', index) raise AbsentSchema('No schema exists on {}'.format(index)) SCHEMA_CACHE[s_key] = json.loads( rv_json ) # cache indexes by both txn# and schema key en passant LOGGER.info('_BaseAgent.get_schema: got schema %s from ledger', index) elif isinstance( index, (int, str) ): # ':2:' not in index - it's a stringified int txn num if it's a str txn_json = await self.get_txn(int(index)) txn = json.loads(txn_json) if txn.get( 'type', None ) == '101': # {} for no such txn; 101 marks indy-sdk schema txn type rv_json = await self.get_schema( SchemaKey(txn['metadata']['from'], txn['data']['data']['name'], txn['data']['data']['version'])) else: LOGGER.info( '_BaseAgent.get_schema: no schema at seq #%s on ledger', index) else: LOGGER.debug( '_BaseAgent.get_schema: <!< bad schema index type') raise AbsentSchema( 'Attempt to get schema on ({}) {} , must use schema key or an int' .format(type(index), index)) LOGGER.debug('_BaseAgent.get_schema <<< %s', rv_json) return rv_json