class ActionReqHandler(RequestHandler): operation_types = {POOL_RESTART, VALIDATOR_INFO} def __init__(self, idrCache: IdrCache, restarter: Restarter, poolManager, poolCfg: PoolConfig, info_tool: ValidatorNodeInfoTool): self.idrCache = idrCache self.restarter = restarter self.info_tool = info_tool self.poolManager = poolManager self.poolCfg = poolCfg self.write_req_validator = WriteRequestValidator(config=getConfig(), auth_map=authMap, cache=self.idrCache, anyone_can_write_map=anyoneCanWriteMap) def doStaticValidation(self, request: Request): pass def validate(self, req: Request): operation = req.operation typ = operation.get(TXN_TYPE) if typ not in self.operation_types: return if typ == POOL_RESTART: action = operation.get(ACTION) self.write_req_validator.validate(req, [AuthActionAdd(txn_type=POOL_RESTART, field=ACTION, value=action)]) elif typ == VALIDATOR_INFO: self.write_req_validator.validate(req, [AuthActionAdd(txn_type=VALIDATOR_INFO, field='*', value='*')]) def apply(self, req: Request, cons_time: int = None): logger.debug("Transaction {} with type {} started" .format(req.reqId, req.txn_type)) try: if req.txn_type == POOL_RESTART: self.restarter.handleRestartRequest(req) result = self._generate_action_result(req) elif req.txn_type == VALIDATOR_INFO: result = self._generate_action_result(req) result[DATA] = self.info_tool.info result[DATA].update(self.info_tool.memory_profiler) result[DATA].update(self.info_tool._generate_software_info()) result[DATA].update(self.info_tool.extractions) result[DATA].update(self.info_tool.node_disk_size) else: raise InvalidClientRequest( "{} is not type of action transaction" .format(req.txn_type)) except Exception as ex: logger.warning("Operation is failed") raise ex logger.debug("Transaction {} with type {} finished" .format(req.reqId, req.txn_type)) return result def _generate_action_result(self, request: Request): return {**request.operation, **{ f.IDENTIFIER.nm: request.identifier, f.REQ_ID.nm: request.reqId}}
class DomainReqHandler(PHandler): write_types = { NYM, ATTRIB, SCHEMA, CLAIM_DEF, REVOC_REG_DEF, REVOC_REG_ENTRY } query_types = { GET_NYM, GET_ATTR, GET_SCHEMA, GET_CLAIM_DEF, GET_REVOC_REG_DEF, GET_REVOC_REG, GET_REVOC_REG_DELTA } revocation_strategy_map = { ISSUANCE_BY_DEFAULT: RevokedStrategy, ISSUANCE_ON_DEMAND: IssuedStrategy, } def __init__(self, ledger, state, config, requestProcessor, idrCache, attributeStore, bls_store, ts_store=None): super().__init__(ledger, state, config, requestProcessor, bls_store, ts_store=ts_store) self.idrCache = idrCache self.attributeStore = attributeStore self.static_validation_handlers = {} self.dynamic_validation_handlers = {} self.state_update_handlers = {} self.query_handlers = {} self.post_batch_creation_handlers = [] self.post_batch_commit_handlers = [] self.post_batch_rejection_handlers = [] self.write_req_validator = WriteRequestValidator( config=getConfig(), auth_map=auth_map, cache=self.idrCache, anyone_can_write_map=anyone_can_write_map) self._add_default_handlers() def _updateStateWithSingleTxn(self, txn, isCommitted=False): txn_type = get_type(txn) if txn_type in self.state_update_handlers: self.state_update_handlers[txn_type](txn, isCommitted) else: logger.debug( 'Cannot apply request of type {} to state'.format(txn_type)) def gen_txn_path(self, txn): """Return path to state as 'str' type or None""" txn_type = get_type(txn) if txn_type not in self.state_update_handlers: logger.error( 'Cannot generate id for txn of type {}'.format(txn_type)) return None if txn_type == NYM: nym = get_payload_data(txn).get(TARGET_NYM) binary_digest = domain.make_state_path_for_nym(nym) return hexlify(binary_digest).decode() elif txn_type == ATTRIB: path = domain.prepare_attr_for_state(txn, path_only=True) return path.decode() elif txn_type == SCHEMA: path = domain.prepare_schema_for_state(txn, path_only=True) return path.decode() elif txn_type == CLAIM_DEF: path = domain.prepare_claim_def_for_state(txn, path_only=True) return path.decode() elif txn_type == REVOC_REG_DEF: path = domain.prepare_revoc_def_for_state(txn, path_only=True) return path.decode() elif txn_type == REVOC_REG_ENTRY: path = domain.prepare_revoc_reg_entry_for_state(txn, path_only=True) return path.decode() raise NotImplementedError( "path construction is not implemented for type {}".format( txn_type)) def commit(self, txnCount, stateRoot, txnRoot, ppTime) -> List: r = super().commit(txnCount, stateRoot, txnRoot, ppTime) self.onBatchCommitted(stateRoot) return r def update_idr_cache(self, stateRoot): stateRoot = base58.b58decode(stateRoot.encode()) self.idrCache.onBatchCommitted(stateRoot) def doStaticValidation(self, request: Request): identifier, req_id, operation = request.identifier, request.reqId, request.operation txn_type = operation[TXN_TYPE] if txn_type in self.static_validation_handlers: self.static_validation_handlers[txn_type](identifier, req_id, operation) def _doStaticValidationNym(self, identifier, reqId, operation): role = operation.get(ROLE) nym = operation.get(TARGET_NYM) if not nym: raise InvalidClientRequest( identifier, reqId, "{} needs to be present".format(TARGET_NYM)) if not Authoriser.isValidRole(role): raise InvalidClientRequest(identifier, reqId, "{} not a valid role".format(role)) def _doStaticValidationAttrib(self, identifier, reqId, operation): if not self._validate_attrib_keys(operation): raise InvalidClientRequest( identifier, reqId, '{} should have one and only one of ' '{}, {}, {}'.format(ATTRIB, RAW, ENC, HASH)) def _doStaticValidationGetRevocRegDelta(self, identifier, reqId, operation): req_ts_to = operation.get(TO, None) assert req_ts_to req_ts_from = operation.get(FROM, None) if req_ts_from and req_ts_from > req_ts_to: raise InvalidClientRequest( identifier, reqId, "Timestamp FROM more then TO: {} > {}".format( req_ts_from, req_ts_to)) def validate(self, req: Request, config=None): op = req.operation txn_type = op[TXN_TYPE] if txn_type in self.dynamic_validation_handlers: self.dynamic_validation_handlers[txn_type](req) @staticmethod def _validate_attrib_keys(operation): dataKeys = {RAW, ENC, HASH}.intersection(set(operation.keys())) return len(dataKeys) == 1 def _validateNym(self, req: Request): op = req.operation nymData = self.idrCache.getNym(op[TARGET_NYM], isCommitted=False) if not nymData: # If nym does not exist self._validateNewNym(req, op) else: self._validateExistingNym(req, op, nymData) def _validateNewNym(self, req: Request, op): role = op.get(ROLE) self.write_req_validator.validate( req, [AuthActionAdd(txn_type=NYM, field=ROLE, value=role)]) def _validateExistingNym(self, req: Request, op, nymData): origin = req.identifier owner = self.idrCache.getOwnerFor(op[TARGET_NYM], isCommitted=False) is_owner = origin == owner updateKeys = [ROLE, VERKEY] for key in updateKeys: if key in op: newVal = op[key] oldVal = nymData.get(key) self.write_req_validator.validate(req, [ AuthActionEdit(txn_type=NYM, field=key, old_value=oldVal, new_value=newVal, is_owner=is_owner) ]) def _validateAttrib(self, req: Request): origin = req.identifier op = req.operation if not (not op.get(TARGET_NYM) or self.hasNym(op[TARGET_NYM], isCommitted=False)): raise InvalidClientRequest( origin, req.reqId, '{} should be added before adding ' 'attribute for it'.format(TARGET_NYM)) if op.get(TARGET_NYM) and op[TARGET_NYM] != req.identifier and \ not self.idrCache.getOwnerFor(op[TARGET_NYM], isCommitted=False) == origin: raise UnauthorizedClientRequest( req.identifier, req.reqId, "Only identity owner/guardian can add attribute " "for that identity") def _validate_schema(self, req: Request): # we can not add a Schema with already existent NAME and VERSION # sine a Schema needs to be identified by seqNo identifier = req.identifier schema_name = get_write_schema_name(req) schema_version = get_write_schema_version(req) schema, _, _, _ = self.getSchema(author=identifier, schemaName=schema_name, schemaVersion=schema_version, with_proof=False) if schema: raise InvalidClientRequest( identifier, req.reqId, '{} can have one and only one SCHEMA with ' 'name {} and version {}'.format(identifier, schema_name, schema_version)) self.write_req_validator.validate( req, [AuthActionAdd(txn_type=SCHEMA, field='*', value='*')]) def _validate_claim_def(self, req: Request): # we can not add a Claim Def with existent ISSUER_DID # sine a Claim Def needs to be identified by seqNo ref = req.operation[REF] try: txn = self.ledger.getBySeqNo(ref) except KeyError: raise InvalidClientRequest( req.identifier, req.reqId, "Mentioned seqNo ({}) doesn't exist.".format(ref)) if txn['txn']['type'] != SCHEMA: raise InvalidClientRequest( req.identifier, req.reqId, "Mentioned seqNo ({}) isn't seqNo of the schema.".format(ref)) # only owner can update claim_def, # because his identifier is the primary key of claim_def self.write_req_validator.validate( req, [AuthActionAdd(txn_type=CLAIM_DEF, field='*', value='*')]) def _validate_revoc_reg_def(self, req: Request): operation = req.operation cred_def_id = operation.get(CRED_DEF_ID) revoc_def_type = operation.get(REVOC_TYPE) revoc_def_tag = operation.get(TAG) assert cred_def_id assert revoc_def_tag assert revoc_def_type tags = cred_def_id.split(":") if len(tags) != 4 and len(tags) != 5: raise InvalidClientRequest( req.identifier, req.reqId, "Format of {} field is not acceptable. " "Expected: 'did:marker:signature_type:schema_ref' or " "'did:marker:signature_type:schema_ref:tag'".format( CRED_DEF_ID)) cred_def, _, _, _ = self.lookup(cred_def_id, isCommitted=False, with_proof=False) if cred_def is None: raise InvalidClientRequest( req.identifier, req.reqId, "There is no any CRED_DEF by path: {}".format(cred_def_id)) def _get_current_revoc_entry_and_revoc_def(self, author_did, revoc_reg_def_id, req_id): assert revoc_reg_def_id current_entry, _, _, _ = self.getRevocDefEntry( revoc_reg_def_id=revoc_reg_def_id, isCommitted=False) revoc_def, _, _, _ = self.lookup(revoc_reg_def_id, isCommitted=False, with_proof=False) if revoc_def is None: raise InvalidClientRequest( author_did, req_id, "There is no any REVOC_REG_DEF by path: {}".format( revoc_reg_def_id)) return current_entry, revoc_def def _validate_revoc_reg_entry(self, req: Request): current_entry, revoc_def = self._get_current_revoc_entry_and_revoc_def( author_did=req.identifier, revoc_reg_def_id=req.operation[REVOC_REG_DEF_ID], req_id=req.reqId) validator_cls = self.get_revocation_strategy( revoc_def[VALUE][ISSUANCE_TYPE]) validator = validator_cls(self.state) validator.validate(current_entry, req) def updateNym(self, nym, txn, isCommitted=True): updatedData = super().updateNym(nym, txn, isCommitted=isCommitted) txn_time = get_txn_time(txn) self.idrCache.set(nym, seqNo=get_seq_no(txn), txnTime=txn_time, ta=updatedData.get(f.IDENTIFIER.nm), role=updatedData.get(ROLE), verkey=updatedData.get(VERKEY), isCommitted=isCommitted) def hasNym(self, nym, isCommitted: bool = True): return self.idrCache.hasNym(nym, isCommitted=isCommitted) def _add_default_handlers(self): self._add_default_static_validation_handlers() self._add_default_dynamic_validation_handlers() self._add_default_state_update_handlers() self._add_default_post_batch_creation_handlers() self._add_default_post_batch_commit_handlers() self._add_default_post_batch_rejection_handlers() self._add_default_query_handlers() def _add_default_static_validation_handlers(self): self.add_static_validation_handler(NYM, self._doStaticValidationNym) self.add_static_validation_handler(ATTRIB, self._doStaticValidationAttrib) self.add_static_validation_handler( GET_REVOC_REG_DELTA, self._doStaticValidationGetRevocRegDelta) def add_static_validation_handler(self, txn_type, handler: Callable): if txn_type in self.static_validation_handlers: raise ValueError('There is already a static validation handler' ' registered for {}'.format(txn_type)) self.static_validation_handlers[txn_type] = handler def _add_default_dynamic_validation_handlers(self): self.add_dynamic_validation_handler(NYM, self._validateNym) self.add_dynamic_validation_handler(ATTRIB, self._validateAttrib) self.add_dynamic_validation_handler(SCHEMA, self._validate_schema) self.add_dynamic_validation_handler(CLAIM_DEF, self._validate_claim_def) self.add_dynamic_validation_handler(REVOC_REG_DEF, self._validate_revoc_reg_def) self.add_dynamic_validation_handler(REVOC_REG_ENTRY, self._validate_revoc_reg_entry) def add_dynamic_validation_handler(self, txn_type, handler: Callable): if txn_type in self.dynamic_validation_handlers: raise ValueError('There is already a dynamic validation handler' ' registered for {}'.format(txn_type)) self.dynamic_validation_handlers[txn_type] = handler def _add_default_state_update_handlers(self): self.add_state_update_handler(NYM, self._addNym) self.add_state_update_handler(ATTRIB, self._addAttr) self.add_state_update_handler(SCHEMA, self._addSchema) self.add_state_update_handler(CLAIM_DEF, self._addClaimDef) self.add_state_update_handler(REVOC_REG_DEF, self._addRevocDef) self.add_state_update_handler(REVOC_REG_ENTRY, self._addRevocRegEntry) def add_state_update_handler(self, txn_type, handler: Callable): if txn_type in self.state_update_handlers: raise ValueError( 'There is already a state update handler registered ' 'for {}'.format(txn_type)) self.state_update_handlers[txn_type] = handler def _add_default_post_batch_creation_handlers(self): self.add_post_batch_creation_handler(self.idrCache.currentBatchCreated) def add_post_batch_creation_handler(self, handler: Callable): if handler in self.post_batch_creation_handlers: raise ValueError('There is already a post batch create handler ' 'registered {}'.format(handler)) self.post_batch_creation_handlers.append(handler) def _add_default_post_batch_commit_handlers(self): self.add_post_batch_commit_handler(self.update_idr_cache) def add_post_batch_commit_handler(self, handler: Callable): if handler in self.post_batch_commit_handlers: raise ValueError('There is already a post batch commit handler ' 'registered {}'.format(handler)) self.post_batch_commit_handlers.append(handler) def _add_default_post_batch_rejection_handlers(self): self.add_post_batch_rejection_handler(self.idrCache.batchRejected) def add_post_batch_rejection_handler(self, handler: Callable): if handler in self.post_batch_rejection_handlers: raise ValueError('There is already a post batch reject handler ' 'registered {}'.format(handler)) self.post_batch_rejection_handlers.append(handler) def _add_default_query_handlers(self): self.add_query_handler(GET_NYM, self.handleGetNymReq) self.add_query_handler(GET_ATTR, self.handleGetAttrsReq) self.add_query_handler(GET_SCHEMA, self.handleGetSchemaReq) self.add_query_handler(GET_CLAIM_DEF, self.handleGetClaimDefReq) self.add_query_handler(GET_REVOC_REG_DEF, self.handleGetRevocRegDefReq) self.add_query_handler(GET_REVOC_REG, self.handleGetRevocRegReq) self.add_query_handler(GET_REVOC_REG_DELTA, self.handleGetRevocRegDelta) def add_query_handler(self, txn_type, handler: Callable): if txn_type in self.query_handlers: raise ValueError('There is already a query handler registered ' 'for {}'.format(txn_type)) self.query_handlers[txn_type] = handler def handleGetNymReq(self, request: Request): nym = request.operation[TARGET_NYM] path = domain.make_state_path_for_nym(nym) nym_data, proof = self.get_value_from_state(path, with_proof=True) if nym_data: nym_data = self.stateSerializer.deserialize(nym_data) nym_data[TARGET_NYM] = nym data = self.stateSerializer.serialize(nym_data) seq_no = nym_data[f.SEQ_NO.nm] update_time = nym_data[TXN_TIME] else: data = None seq_no = None update_time = None # TODO: add update time here! result = self.make_result(request=request, data=data, last_seq_no=seq_no, update_time=update_time, proof=proof) result.update(request.operation) return result def handleGetSchemaReq(self, request: Request): author_did = get_read_schema_from(request) schema_name = get_read_schema_name(request) schema_version = get_read_schema_version(request) schema, lastSeqNo, lastUpdateTime, proof = self.getSchema( author=author_did, schemaName=schema_name, schemaVersion=schema_version, with_proof=True) # TODO: we have to do this since SCHEMA has a bit different format than other txns # (it has NAME and VERSION inside DATA, and it's not part of the state value, but state key) if schema is None: schema = {} schema.update({ SCHEMA_NAME: schema_name, SCHEMA_VERSION: schema_version }) return self.make_result(request=request, data=schema, last_seq_no=lastSeqNo, update_time=lastUpdateTime, proof=proof) def handleGetClaimDefReq(self, request: Request): frm = get_read_claim_def_from(request) signature_type = get_read_claim_def_signature_type(request) schema_ref = get_read_claim_def_schema_ref(request) tag = get_read_claim_def_tag(request) keys, lastSeqNo, lastUpdateTime, proof = self.getClaimDef( author=frm, schemaSeqNo=schema_ref, signatureType=signature_type, tag=tag) result = self.make_result(request=request, data=keys, last_seq_no=lastSeqNo, update_time=lastUpdateTime, proof=proof) result[CLAIM_DEF_SIGNATURE_TYPE] = signature_type return result def handleGetRevocRegDefReq(self, request: Request): state_path = request.operation.get(ID, None) assert state_path try: keys, last_seq_no, last_update_time, proof = self.lookup( state_path, isCommitted=True, with_proof=True) except KeyError: keys, last_seq_no, last_update_time, proof = None, None, None, None result = self.make_result(request=request, data=keys, last_seq_no=last_seq_no, update_time=last_update_time, proof=proof) return result def handleGetRevocRegReq(self, request: Request): req_ts = request.operation.get(TIMESTAMP) revoc_reg_def_id = request.operation.get(REVOC_REG_DEF_ID) # Get root hash corresponding with given timestamp past_root = self.ts_store.get_equal_or_prev(req_ts) # Path to corresponding ACCUM record in state path = domain.make_state_path_for_revoc_reg_entry_accum( revoc_reg_def_id=revoc_reg_def_id) entry_state = StateValue() if past_root is not None: encoded_entry, proof = self.get_value_from_state( path, head_hash=past_root, with_proof=True) entry_state.proof = proof if encoded_entry: revoc_reg_entry_accum, seq_no, last_update_time = domain.decode_state_value( encoded_entry) entry_state = StateValue(root_hash=past_root, value=revoc_reg_entry_accum, seq_no=seq_no, update_time=last_update_time, proof=proof) return self.make_result(request=request, data=entry_state.value, last_seq_no=entry_state.seq_no, update_time=entry_state.update_time, proof=entry_state.proof) def _get_reg_entry_by_timestamp(self, timestamp, path_to_reg_entry): reg_entry = None seq_no = None last_update_time = None reg_entry_proof = None past_root = self.ts_store.get_equal_or_prev(timestamp) if past_root: encoded_entry, reg_entry_proof = self.get_value_from_state( path_to_reg_entry, head_hash=past_root, with_proof=True) if encoded_entry: reg_entry, seq_no, last_update_time = domain.decode_state_value( encoded_entry) return StateValue(root_hash=past_root, value=reg_entry, seq_no=seq_no, update_time=last_update_time, proof=reg_entry_proof) def _get_reg_entry_accum_by_timestamp(self, timestamp, path_to_reg_entry_accum): reg_entry_accum = None seq_no = None last_update_time = None reg_entry_accum_proof = None past_root = self.ts_store.get_equal_or_prev(timestamp) if past_root: encoded_entry, reg_entry_accum_proof = self.get_value_from_state( path_to_reg_entry_accum, head_hash=past_root, with_proof=True) if encoded_entry: reg_entry_accum, seq_no, last_update_time = domain.decode_state_value( encoded_entry) return StateValue(root_hash=past_root, value=reg_entry_accum, seq_no=seq_no, update_time=last_update_time, proof=reg_entry_accum_proof) def handleGetRevocRegDelta(self, request: Request): """ For getting reply we need: 1. Get REVOC_REG_ENTRY by "TO" timestamp from state 2. If FROM is given in request, then Get REVOC_REG_ENTRY by "FROM" timestamp from state 3. Get ISSUANCE_TYPE for REVOC_REG_DEF (revoked/issued strategy) 4. Compute issued and revoked indices by corresponding strategy 5. Make result 5.1 Now, if "FROM" is presented in request, then STATE_PROOF_FROM and ACCUM (revocation entry for "FROM" timestamp) will added into data section 5.2 If not, then only STATE_PROOF for "TO" revocation entry will added :param request: :return: Reply """ req_ts_from = request.operation.get(FROM, None) req_ts_to = request.operation.get(TO) revoc_reg_def_id = request.operation.get(REVOC_REG_DEF_ID) reply = None """ Get root hash for "to" timestamp Get REVOC_REG_ENTRY and ACCUM record for timestamp "to" """ path_to_reg_entry = domain.make_state_path_for_revoc_reg_entry( revoc_reg_def_id=revoc_reg_def_id) path_to_reg_entry_accum = domain.make_state_path_for_revoc_reg_entry_accum( revoc_reg_def_id=revoc_reg_def_id) entry_to = self._get_reg_entry_by_timestamp(req_ts_to, path_to_reg_entry) accum_to = self._get_reg_entry_accum_by_timestamp( req_ts_to, path_to_reg_entry_accum) entry_from = StateValue() accum_from = StateValue() if accum_to.value and entry_to.value: """Get issuance type from REVOC_REG_DEF""" encoded_revoc_reg_def = self.state.get_for_root_hash( entry_to.root_hash, revoc_reg_def_id) if encoded_revoc_reg_def: revoc_reg_def, _, _ = domain.decode_state_value( encoded_revoc_reg_def) strategy_cls = self.get_revocation_strategy( revoc_reg_def[VALUE][ISSUANCE_TYPE]) issued_to = entry_to.value[VALUE].get(ISSUED, []) revoked_to = entry_to.value[VALUE].get(REVOKED, []) if req_ts_from: """Get REVOC_REG_ENTRY and ACCUM records for timestamp from if exist""" entry_from = self._get_reg_entry_by_timestamp( req_ts_from, path_to_reg_entry) accum_from = self._get_reg_entry_accum_by_timestamp( req_ts_from, path_to_reg_entry_accum) if req_ts_from and entry_from.value and accum_from.value: """Compute issued/revoked lists corresponding with ISSUANCE_TYPE strategy""" issued_from = entry_from.value[VALUE].get(ISSUED, []) revoked_from = entry_from.value[VALUE].get(REVOKED, []) result_issued, result_revoked = strategy_cls.get_delta( { ISSUED: issued_to, REVOKED: revoked_to }, { ISSUED: issued_from, REVOKED: revoked_from }) else: result_issued, result_revoked = strategy_cls.get_delta( { ISSUED: issued_to, REVOKED: revoked_to }, None) reply = { REVOC_REG_DEF_ID: revoc_reg_def_id, REVOC_TYPE: revoc_reg_def.get(REVOC_TYPE), VALUE: { ACCUM_TO: accum_to.value if entry_from.value else entry_to.value, ISSUED: result_issued, REVOKED: result_revoked } } """If we got "from" timestamp, then add state proof into "data" section of reply""" if req_ts_from and accum_from.value: reply[STATE_PROOF_FROM] = accum_from.proof reply[VALUE][ACCUM_FROM] = accum_from.value if accum_to and entry_to: seq_no = accum_to.seq_no if entry_from.value else entry_to.seq_no update_time = accum_to.update_time if entry_from.value else entry_to.update_time proof = accum_to.proof if entry_from.value else entry_to.proof else: seq_no = None update_time = None proof = None return self.make_result(request=request, data=reply, last_seq_no=seq_no, update_time=update_time, proof=proof) def handleGetAttrsReq(self, request: Request): if not self._validate_attrib_keys(request.operation): raise InvalidClientRequest( request.identifier, request.reqId, '{} should have one and only one of ' '{}, {}, {}'.format(ATTRIB, RAW, ENC, HASH)) nym = request.operation[TARGET_NYM] if RAW in request.operation: attr_type = RAW elif ENC in request.operation: # If attribute is encrypted, it will be queried by its hash attr_type = ENC else: attr_type = HASH attr_key = request.operation[attr_type] value, lastSeqNo, lastUpdateTime, proof = \ self.getAttr(did=nym, key=attr_key, attr_type=attr_type) attr = None if value is not None: if HASH in request.operation: attr = attr_key else: attr = value return self.make_result(request=request, data=attr, last_seq_no=lastSeqNo, update_time=lastUpdateTime, proof=proof) def lookup(self, path, isCommitted=True, with_proof=False) -> (str, int): """ Queries state for data on specified path :param path: path to data :param isCommitted: queries the committed state root if True else the uncommitted root :param with_proof: creates proof if True :return: data """ assert path is not None head_hash = self.state.committedHeadHash if isCommitted else self.state.headHash encoded, proof = self.get_value_from_state(path, head_hash, with_proof=with_proof) if encoded: value, last_seq_no, last_update_time = domain.decode_state_value( encoded) return value, last_seq_no, last_update_time, proof return None, None, None, proof def _addNym(self, txn, isCommitted=False) -> None: txn_data = get_payload_data(txn) nym = txn_data.get(TARGET_NYM) data = { f.IDENTIFIER.nm: get_from(txn), f.SEQ_NO.nm: get_seq_no(txn), TXN_TIME: get_txn_time(txn) } if ROLE in txn_data: data[ROLE] = txn_data.get(ROLE) if VERKEY in txn_data: data[VERKEY] = txn_data.get(VERKEY) self.updateNym(nym, txn, isCommitted=isCommitted) def _addAttr(self, txn, isCommitted=False) -> None: """ The state trie stores the hash of the whole attribute data at: the did+attribute name if the data is plaintext (RAW) the did+hash(attribute) if the data is encrypted (ENC) If the attribute is HASH, then nothing is stored in attribute store, the trie stores a blank value for the key did+hash """ assert get_type(txn) == ATTRIB attr_type, path, value, hashed_value, value_bytes = domain.prepare_attr_for_state( txn) self.state.set(path, value_bytes) if attr_type != HASH: self.attributeStore.set(hashed_value, value) def _addSchema(self, txn, isCommitted=False) -> None: assert get_type(txn) == SCHEMA path, value_bytes = domain.prepare_schema_for_state(txn) self.state.set(path, value_bytes) def _addClaimDef(self, txn, isCommitted=False) -> None: assert get_type(txn) == CLAIM_DEF path, value_bytes = domain.prepare_claim_def_for_state(txn) self.state.set(path, value_bytes) def _addRevocDef(self, txn, isCommitted=False) -> None: assert get_type(txn) == REVOC_REG_DEF path, value_bytes = domain.prepare_revoc_def_for_state(txn) self.state.set(path, value_bytes) def _addRevocRegEntry(self, txn, isCommitted=False) -> None: current_entry, revoc_def = self._get_current_revoc_entry_and_revoc_def( author_did=get_from(txn), revoc_reg_def_id=get_payload_data(txn)[REVOC_REG_DEF_ID], req_id=get_req_id(txn)) writer_cls = self.get_revocation_strategy( revoc_def[VALUE][ISSUANCE_TYPE]) writer = writer_cls(self.state) writer.write(current_entry, txn) def onBatchCreated(self, stateRoot): for handler in self.post_batch_creation_handlers: handler(stateRoot) def onBatchRejected(self): for handler in self.post_batch_rejection_handlers: handler() def onBatchCommitted(self, stateRoot): for handler in self.post_batch_commit_handlers: handler(stateRoot) def getAttr(self, did: str, key: str, attr_type, isCommitted=True) -> (str, int, int, list): assert did is not None assert key is not None path = domain.make_state_path_for_attr(did, key, attr_type == HASH) try: hashed_val, lastSeqNo, lastUpdateTime, proof = \ self.lookup(path, isCommitted, with_proof=True) except KeyError: return None, None, None, None if not hashed_val or hashed_val == '': # Its a HASH attribute return hashed_val, lastSeqNo, lastUpdateTime, proof else: try: value = self.attributeStore.get(hashed_val) except KeyError: logger.error( 'Could not get value from attribute store for {}'.format( hashed_val)) return None, None, None, None return value, lastSeqNo, lastUpdateTime, proof def getSchema(self, author: str, schemaName: str, schemaVersion: str, isCommitted=True, with_proof=True) -> (str, int, int, list): assert author is not None assert schemaName is not None assert schemaVersion is not None path = domain.make_state_path_for_schema(author, schemaName, schemaVersion) try: keys, seqno, lastUpdateTime, proof = self.lookup( path, isCommitted, with_proof=with_proof) return keys, seqno, lastUpdateTime, proof except KeyError: return None, None, None, None def getClaimDef(self, author: str, schemaSeqNo: str, signatureType, tag, isCommitted=True) -> (str, int, int, list): assert author is not None assert schemaSeqNo is not None path = domain.make_state_path_for_claim_def(author, schemaSeqNo, signatureType, tag) try: keys, seqno, lastUpdateTime, proof = self.lookup(path, isCommitted, with_proof=True) return keys, seqno, lastUpdateTime, proof except KeyError: return None, None, None, None def getRevocDef(self, author_did, cred_def_id, revoc_def_type, revoc_def_tag, isCommitted=True) -> (str, int, int, list): assert author_did is not None assert cred_def_id is not None assert revoc_def_type is not None assert revoc_def_tag is not None path = domain.make_state_path_for_revoc_def(author_did, cred_def_id, revoc_def_type, revoc_def_tag) try: keys, seqno, lastUpdateTime, proof = self.lookup(path, isCommitted, with_proof=True) return keys, seqno, lastUpdateTime, proof except KeyError: return None, None, None, None def getRevocDefEntry(self, revoc_reg_def_id, isCommitted=True) -> (str, int, int, list): assert revoc_reg_def_id path = domain.make_state_path_for_revoc_reg_entry( revoc_reg_def_id=revoc_reg_def_id) try: keys, seqno, lastUpdateTime, proof = self.lookup(path, isCommitted, with_proof=False) return keys, seqno, lastUpdateTime, proof except KeyError: return None, None, None, None def get_revocation_strategy(self, typ): return self.revocation_strategy_map.get(typ) def get_query_response(self, request: Request): return self.query_handlers[request.operation[TXN_TYPE]](request) @staticmethod def transform_txn_for_ledger(txn): """ Some transactions need to be transformed before they can be stored in the ledger, eg. storing certain payload in another data store and only its hash in the ledger """ if get_type(txn) == ATTRIB: txn = DomainReqHandler.transform_attrib_for_ledger(txn) return txn @staticmethod def transform_attrib_for_ledger(txn): """ Creating copy of result so that `RAW`, `ENC` or `HASH` can be replaced by their hashes. We do not insert actual attribute data in the ledger but only the hash of it. """ txn = deepcopy(txn) txn_data = get_payload_data(txn) attr_type, _, value = domain.parse_attr_txn(txn_data) if attr_type in [RAW, ENC]: txn_data[attr_type] = domain.hash_of(value) if value else '' return txn
class PoolRequestHandler(PHandler): def __init__(self, ledger: Ledger, state: State, states, idrCache: IdrCache): super().__init__(ledger, state, states) self.stateSerializer = pool_state_serializer self.idrCache = idrCache self.write_req_validator = WriteRequestValidator( config=getConfig(), auth_map=auth_map, cache=self.idrCache, anyone_can_write_map=anyone_can_write_map) def isSteward(self, nym, isCommitted: bool = True): return self.idrCache.hasSteward(nym, isCommitted) def authErrorWhileAddingNode(self, request): origin = request.identifier operation = request.operation data = operation.get(DATA, {}) error = self.dataErrorWhileValidating(data, skipKeys=False) if error: return error if self.stewardHasNode(origin): return "{} already has a node".format(origin) error = self.isNodeDataConflicting(data) if error: return "existing data has conflicts with " \ "request data {}. Error: {}".format(operation.get(DATA), error) self.write_req_validator.validate(request, [ AuthActionAdd(txn_type=NODE, field=SERVICES, value=data.get(SERVICES, [VALIDATOR])) ]) def authErrorWhileUpdatingNode(self, request): origin = request.identifier isTrustee = self.idrCache.hasTrustee(origin, isCommitted=False) if not isTrustee: error = super().authErrorWhileUpdatingNode(request) if error: return error origin = request.identifier operation = request.operation nodeNym = operation.get(TARGET_NYM) data = operation.get(DATA, {}) error = self.dataErrorWhileValidatingUpdate(data, nodeNym) if error: return error isStewardOfNode = self.isStewardOfNode(origin, nodeNym, isCommitted=False) nodeInfo = self.getNodeData(nodeNym, isCommitted=False) data = deepcopy(data) data.pop(ALIAS, None) for k in data: if k == BLS_KEY_PROOF: continue oldVal = nodeInfo.get(k, None) if nodeInfo else None newVal = data[k] if k == SERVICES: if not oldVal: oldVal = [] if not newVal: newVal = [] if oldVal != newVal: self.write_req_validator.validate(request, [ AuthActionEdit(txn_type=NODE, field=k, old_value=oldVal, new_value=newVal, is_owner=isStewardOfNode) ])
class ConfigReqHandler(LedgerRequestHandler): write_types = {POOL_UPGRADE, NODE_UPGRADE, POOL_CONFIG} def __init__(self, ledger, state, idrCache: IdrCache, upgrader: Upgrader, poolManager, poolCfg: PoolConfig): super().__init__(ledger, state) self.idrCache = idrCache self.upgrader = upgrader self.poolManager = poolManager self.poolCfg = poolCfg self.write_req_validator = WriteRequestValidator( config=getConfig(), auth_map=authMap, cache=self.idrCache, anyone_can_write_map=anyoneCanWriteMap) def doStaticValidation(self, request: Request): identifier, req_id, operation = request.identifier, request.reqId, request.operation if operation[TXN_TYPE] == POOL_UPGRADE: self._doStaticValidationPoolUpgrade(identifier, req_id, operation) elif operation[TXN_TYPE] == POOL_CONFIG: self._doStaticValidationPoolConfig(identifier, req_id, operation) def _doStaticValidationPoolConfig(self, identifier, reqId, operation): pass def _doStaticValidationPoolUpgrade(self, identifier, reqId, operation): action = operation.get(ACTION) if action not in (START, CANCEL): raise InvalidClientRequest(identifier, reqId, "{} not a valid action".format(action)) if action == START: schedule = operation.get(SCHEDULE, {}) force = operation.get(FORCE) force = str(force) == 'True' isValid, msg = self.upgrader.isScheduleValid( schedule, self.poolManager.getNodesServices(), force) if not isValid: raise InvalidClientRequest( identifier, reqId, "{} not a valid schedule since {}".format(schedule, msg)) # TODO: Check if cancel is submitted before start def curr_pkt_info(self, pkg_name): if pkg_name == APP_NAME: return Upgrader.getVersion(), [APP_NAME] return NodeControlUtil.curr_pkt_info(pkg_name) def validate(self, req: Request): status = '*' operation = req.operation typ = operation.get(TXN_TYPE) if typ not in [POOL_UPGRADE, POOL_CONFIG]: return if typ == POOL_UPGRADE: pkt_to_upgrade = req.operation.get(PACKAGE, getConfig().UPGRADE_ENTRY) if pkt_to_upgrade: currentVersion, cur_deps = self.curr_pkt_info(pkt_to_upgrade) if not currentVersion: raise InvalidClientRequest( req.identifier, req.reqId, "Packet {} is not installed and cannot be upgraded". format(pkt_to_upgrade)) if all([APP_NAME not in d for d in cur_deps]): raise InvalidClientRequest( req.identifier, req.reqId, "Packet {} doesn't belong to pool".format( pkt_to_upgrade)) else: raise InvalidClientRequest(req.identifier, req.reqId, "Upgrade packet name is empty") targetVersion = req.operation[VERSION] reinstall = req.operation.get(REINSTALL, False) if not Upgrader.is_version_upgradable(currentVersion, targetVersion, reinstall): # currentVersion > targetVersion raise InvalidClientRequest(req.identifier, req.reqId, "Version is not upgradable") action = operation.get(ACTION) # TODO: Some validation needed for making sure name and version # present txn = self.upgrader.get_upgrade_txn( lambda txn: get_payload_data(txn).get(NAME, None) == req. operation.get(NAME, None) and get_payload_data(txn).get( VERSION) == req.operation.get(VERSION), reverse=True) if txn: status = get_payload_data(txn).get(ACTION, '*') if status == START and action == START: raise InvalidClientRequest( req.identifier, req.reqId, "Upgrade '{}' is already scheduled".format( req.operation.get(NAME))) if status == '*': auth_action = AuthActionAdd(txn_type=POOL_UPGRADE, field=ACTION, value=action) else: auth_action = AuthActionEdit(txn_type=POOL_UPGRADE, field=ACTION, old_value=status, new_value=action) self.write_req_validator.validate(req, [auth_action]) elif typ == POOL_CONFIG: action = '*' status = '*' self.write_req_validator.validate(req, [ AuthActionEdit(txn_type=typ, field=ACTION, old_value=status, new_value=action) ]) def apply(self, req: Request, cons_time): txn = append_txn_metadata(reqToTxn(req), txn_time=cons_time) self.ledger.append_txns_metadata([txn]) (start, _), _ = self.ledger.appendTxns([txn]) return start, txn def commit(self, txnCount, stateRoot, txnRoot, ppTime) -> List: committedTxns = super().commit(txnCount, stateRoot, txnRoot, ppTime) for txn in committedTxns: # Handle POOL_UPGRADE or POOL_CONFIG transaction here # only in case it is not forced. # If it is forced then it was handled earlier # in applyForced method. if not is_forced(txn): self.upgrader.handleUpgradeTxn(txn) self.poolCfg.handleConfigTxn(txn) return committedTxns def applyForced(self, req: Request): super().applyForced(req) txn = reqToTxn(req) self.upgrader.handleUpgradeTxn(txn) self.poolCfg.handleConfigTxn(txn)