From 456dcef9b8c9d57eec44799e6baf2d2858c6e866 Mon Sep 17 00:00:00 2001 From: Atharva Amritkar Date: Tue, 27 Feb 2024 20:02:47 +0400 Subject: [PATCH] plugin:adding did plugin support in indy-plenum Signed-off-by: Atharva Amritkar --- .../did_plugin/Functionalities/OU_DID.py | 63 ++++++ plenum/server/plugin/did_plugin/__init__.py | 20 ++ .../did_plugin/batch_handlers/__init__.py | 0 .../did_plugin_batch_handler.py | 16 ++ plenum/server/plugin/did_plugin/common.py | 191 +++++++++++++++++ plenum/server/plugin/did_plugin/config.py | 8 + plenum/server/plugin/did_plugin/conftest.py | 82 +++++++ plenum/server/plugin/did_plugin/constants.py | 15 ++ plenum/server/plugin/did_plugin/helper.py | 67 ++++++ plenum/server/plugin/did_plugin/main.py | 64 ++++++ .../did_plugin/request_handlers/__init__.py | 0 .../abstract_did_req_handler.py | 51 +++++ .../request_handlers/create_did_handler.py | 132 ++++++++++++ .../create_network_did_handler.py | 200 ++++++++++++++++++ .../request_handlers/create_org_unit_id.py | 129 +++++++++++ .../create_security_domain_did.py | 115 ++++++++++ .../did_plugin/request_handlers/fetch_did.py | 31 +++ plenum/server/plugin/did_plugin/storage.py | 24 +++ .../server/plugin/did_plugin/test_catchup.py | 74 +++++++ .../plugin/did_plugin/test_freshness.py | 37 ++++ .../test_freshness_during_ordering.py | 32 +++ .../plugin/did_plugin/test_frozen_ledgers.py | 74 +++++++ .../plugin/did_plugin/test_plugin_basic.py | 50 +++++ .../plugin/did_plugin/test_plugin_removing.py | 126 +++++++++++ .../test_plugin_request_handling.py | 149 +++++++++++++ .../plugin/did_plugin/test_request_digest.py | 78 +++++++ .../server/plugin/did_plugin/transactions.py | 21 ++ 27 files changed, 1849 insertions(+) create mode 100644 plenum/server/plugin/did_plugin/Functionalities/OU_DID.py create mode 100644 plenum/server/plugin/did_plugin/__init__.py create mode 100644 plenum/server/plugin/did_plugin/batch_handlers/__init__.py create mode 100644 plenum/server/plugin/did_plugin/batch_handlers/did_plugin_batch_handler.py create mode 100644 plenum/server/plugin/did_plugin/common.py create mode 100644 plenum/server/plugin/did_plugin/config.py create mode 100644 plenum/server/plugin/did_plugin/conftest.py create mode 100644 plenum/server/plugin/did_plugin/constants.py create mode 100644 plenum/server/plugin/did_plugin/helper.py create mode 100644 plenum/server/plugin/did_plugin/main.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/__init__.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/abstract_did_req_handler.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/create_did_handler.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/create_network_did_handler.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/create_org_unit_id.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/create_security_domain_did.py create mode 100644 plenum/server/plugin/did_plugin/request_handlers/fetch_did.py create mode 100644 plenum/server/plugin/did_plugin/storage.py create mode 100644 plenum/server/plugin/did_plugin/test_catchup.py create mode 100644 plenum/server/plugin/did_plugin/test_freshness.py create mode 100644 plenum/server/plugin/did_plugin/test_freshness_during_ordering.py create mode 100644 plenum/server/plugin/did_plugin/test_frozen_ledgers.py create mode 100644 plenum/server/plugin/did_plugin/test_plugin_basic.py create mode 100644 plenum/server/plugin/did_plugin/test_plugin_removing.py create mode 100644 plenum/server/plugin/did_plugin/test_plugin_request_handling.py create mode 100644 plenum/server/plugin/did_plugin/test_request_digest.py create mode 100644 plenum/server/plugin/did_plugin/transactions.py diff --git a/plenum/server/plugin/did_plugin/Functionalities/OU_DID.py b/plenum/server/plugin/did_plugin/Functionalities/OU_DID.py new file mode 100644 index 0000000000..3abc04f8b3 --- /dev/null +++ b/plenum/server/plugin/did_plugin/Functionalities/OU_DID.py @@ -0,0 +1,63 @@ +import json +import base64 +from nacl.signing import VerifyKey +from nacl.exceptions import BadSignatureError + +""" +1. The function wil recieve REQUEST_BODY somehow +2. The REQUEST_BODY will have `DIDDocment`which will have all the info.... +3. I will get `publicKeyMultibase` for verification purposes. +4. Now get the `VerificationKey` and `Signature` from REQUEST_BODY. +5. Then tried to verify it.... +""" + +request = { + "identifier": "EbP4aYNeTHL6q385GuVpRV", + "operation": { + "dest": "Vzfdscz6YG6n1EuNJV4ob1", + "type": "20226", + "data": { + "DIDDocument": { + "id": "did:exampleiin:org1", + "verificationMethod": [ + { + "id": "did:exampleiin:org1#key1", + "type": "Ed25519VerificationKey2020", + "controller": "did:exampleiin:org1", + "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ], + "authentication": ["did:exampleiin:org1"] + }, + "signature": { + "verificationMethod": "did:exampleiin:org1#key1", + "sigbase64": "sdfsdfsdf" + } + }, + "verkey": "~HFPBKb7S7ocrTzxakNbcao" + }, + "protocolVersion": 2, + "reqId": 1704282737760629997 +} + +# Get DID document +did_doc = json.loads(request["operation"]["data"]["DIDDocument"]) + +# Get public key from DID document +key_id = "did:exampleiin:org1#key1" +public_key = did_doc["verificationMethod"][0]["publicKeyMultibase"] + +# Load public key +verify_key = VerifyKey(base64.standard_b64decode(public_key)) + +# Get signature +signature = base64.standard_b64decode(request["operation"]["data"]["signature"]["sigbase64"]) + +# Get signed data +signed_data = json.dumps(did_doc) + +try: + verify_key.verify(signed_data.encode(), signature) + print("Signature is valid!") +except BadSignatureError: + print("Invalid signature!") \ No newline at end of file diff --git a/plenum/server/plugin/did_plugin/__init__.py b/plenum/server/plugin/did_plugin/__init__.py new file mode 100644 index 0000000000..e3f7a599e8 --- /dev/null +++ b/plenum/server/plugin/did_plugin/__init__.py @@ -0,0 +1,20 @@ +from plenum.common.messages.fields import FixedLengthField +from plenum.server.plugin.did_plugin.transactions import DemoTransactions +from plenum.server.plugin.did_plugin.constants import DID_PLUGIN_LEDGER_ID + + +dummy_field_length = 10 +LEDGER_IDS = {DID_PLUGIN_LEDGER_ID, } +CLIENT_REQUEST_FIELDS = {'fix_length_dummy': + FixedLengthField(dummy_field_length, + optional=True, nullable=True)} + +AcceptableWriteTypes = {DemoTransactions.CREATE_DID.value, + DemoTransactions.CREATE_NETWORK_DID.value, + DemoTransactions.UPDATE_DID.value, + DemoTransactions.UPDATE_NETWORK_DID.value, + DemoTransactions.DEACTIVATE_DID.value, + DemoTransactions.OUDID.value +} + +AcceptableQueryTypes = {DemoTransactions.FETCH_DID.value, } diff --git a/plenum/server/plugin/did_plugin/batch_handlers/__init__.py b/plenum/server/plugin/did_plugin/batch_handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plenum/server/plugin/did_plugin/batch_handlers/did_plugin_batch_handler.py b/plenum/server/plugin/did_plugin/batch_handlers/did_plugin_batch_handler.py new file mode 100644 index 0000000000..cd27a007b8 --- /dev/null +++ b/plenum/server/plugin/did_plugin/batch_handlers/did_plugin_batch_handler.py @@ -0,0 +1,16 @@ +from plenum.common.constants import DOMAIN_LEDGER_ID +from plenum.server.batch_handlers.batch_request_handler import BatchRequestHandler +from plenum.server.database_manager import DatabaseManager +from plenum.server.plugin.did_plugin import DID_PLUGIN_LEDGER_ID + + +class DIDBatchHandler(BatchRequestHandler): + + def __init__(self, database_manager: DatabaseManager): + super().__init__(database_manager, DID_PLUGIN_LEDGER_ID) + + def post_batch_applied(self, three_pc_batch, prev_handler_result=None): + pass + + def post_batch_rejected(self, ledger_id, prev_handler_result=None): + pass diff --git a/plenum/server/plugin/did_plugin/common.py b/plenum/server/plugin/did_plugin/common.py new file mode 100644 index 0000000000..35a4790e32 --- /dev/null +++ b/plenum/server/plugin/did_plugin/common.py @@ -0,0 +1,191 @@ +import json +import libnacl +import libnacl.encode +from plenum.common.exceptions import InvalidSignature + +def libnacl_validate(vk_base64, signature_base64, originalhash): + vk = libnacl.encode.base64_decode(vk_base64) + signature = libnacl.encode.base64_decode(signature_base64) + verifiedhash = libnacl.crypto_sign_open(signature, vk) + if signature == originalhash: + raise InvalidSignature("The hash of the DIDDocument did not match.") +# +def libnacl_validate2(vk_base64, signature_base64): + print("vk_base64", vk_base64) + print("signature_base64", signature_base64) + # vk = libnacl.encode.base64_decode(vk_base64) + # signature = libnacl.encode.base64_decode(signature_base64) + # verifiedhash = libnacl.crypto_sign_open(signature, vk) + return signature_base64 + +def did_id_from_url(did_url: str) -> str: + return did_url.split("#")[0] + + +class DID: + did = None + id = None + verification_methods = None + authentication_methods = None + + def __init__(self, did_json) -> None: + self.did = json.loads(did_json) + self.id = self.did["id"] + + # populate verification methods: + self.verification_methods = {} + for method in self.did["verificationMethod"]: + self.verification_methods[method["id"]] = method + + # populate authentication methods: + self.authentication_methods = {} + for method in self.did["authentication"]: + if isinstance(method, dict): + # fully specified method + self.authentication_methods[method["id"]] = method + elif isinstance(method, str): + # id points to a verification method + # TODO: if it points to a different did -> resolve that did and fetch method + if method in self.verification_methods: + self.authentication_methods[method] = self.verification_methods[method] +# [{'controller': 'did:exampleiin:org1', 'id': 'did:exampleiin:org1#key1', 'publicKeyMultibase': '4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA', 'type': 'libnacl'}]} + def fetch_authentication_method(self, authentication_method_id: str) -> dict: + # if authentication_method_id in self.authentication_methods: + # return self.authentication_methods[authentication_method_id] + return authentication_method_id[0] + + def fetch_authentication(self, authentication_method_id: str) -> dict: + if authentication_method_id in self.authentication_methods: + return self.authentication_methods[authentication_method_id] + return None + + + +class NetworkDID: + did = None + id = None + verification_methods = None + authentication_methods = None + network_participants: list = None + + def __init__(self, did_json) -> None: + self.did = json.loads(did_json) + self.id = self.did["id"] + self.network_participants = self.did["networkMembers"] + + assert(len(self.network_participants) > 0) + + # populate verification methods: + self.verification_methods = {} + for method in self.did["verificationMethod"]: + self.verification_methods[method["id"]] = method + + # populate authentication methods: + self.authentication_methods = {} + for method in self.did["authentication"]: + if isinstance(method, dict): + # fully specified method + self.authentication_methods[method["id"]] = method + elif isinstance(method, str): + # id points to a verification method + # TODO: if it points to a different did -> resolve that did and fetch method + if method in self.verification_methods: + self.authentication_methods[method] = self.verification_methods[method] + + # ensure atleast one authentication method of type GroupMultiSig + group_multisig_auth_support = False + for method_id, method in self.authentication_methods.items(): + if method["type"] == "BlockchainNetworkMultiSig": + group_multisig_auth_support = True + if not group_multisig_auth_support: + raise Exception("Network DID does not have BlockchainNetworkMultiSig authentication method") + + # Get any one authentication method of type GroupMultiSig + def fetch_authentication_method(self) -> dict: + for method_id, method in self.authentication_methods.items(): + if method["type"] == "BlockchainNetworkMultiSig": + return method + + def fetch_signature(self) -> dict: + + + + +class OUDID: + did = None + id = None + verification_methods = None + authentication_methods = None + + def __init__(self, did_json) -> None: + self.did = json.loads(did_json) + self.id = self.did["id"] + + # populate verification methods: + self.verification_methods = {} + for method in self.did["verificationMethod"]: + self.verification_methods[method["id"]] = method + + # "authentication": ["did::"] + # populate authentication methods: + self.authentication_methods = {} + for method in self.did["authentication"]: + if isinstance(method, dict): + # fully specified method + self.authentication_methods[method["id"]] = method + elif isinstance(method, str): + # id points to a verification method + # TODO: if it points to a different did -> resolve that did and fetch method + if method in self.verification_methods: + self.authentication_methods[method] = self.verification_methods[method] + + def fetch_authentication_method(self) -> dict: + if authentication_method_id in self.authentication_methods: + return self.authentication_methods[authentication_method_id] + return None + + +class SDDID: + did = None + id = None + verification_methods = None + authentication_methods = None + network_participants: list = None + + def __init__(self, did_json) -> None: + self.did = json.loads(did_json) + self.id = self.did["id"] + self.network_participants = self.did["networkMembers"] + + assert(len(self.network_participants) > 0) + + # populate verification methods: + self.verification_methods = {} + for method in self.did["verificationMethod"]: + self.verification_methods[method["id"]] = method + + # populate authentication methods: + self.authentication_methods = {} + for method in self.did["authentication"]: + if isinstance(method, dict): + # fully specified method + self.authentication_methods[method["id"]] = method + elif isinstance(method, str): + # id points to a verification method + # TODO: if it points to a different did -> resolve that did and fetch method + if method in self.verification_methods: + self.authentication_methods[method] = self.verification_methods[method] + + # ensure atleast one authentication method of type BlockchainNetworkMultiSig + group_multisig_auth_support = False + for method_id, method in self.authentication_methods.items(): + if method["type"] == "BlockchainNetworkMultiSig": + group_multisig_auth_support = True + if not group_multisig_auth_support: + raise Exception("Network DID does not have BlockchainNetworkMultiSig authentication method") + + # Get any one authentication method of type BlockchainNetworkMultiSig + def fetch_authentication_method(self) -> dict: + for method_id, method in self.authentication_methods.items(): + if method["type"] == "BlockchainNetworkMultiSig": + return method \ No newline at end of file diff --git a/plenum/server/plugin/did_plugin/config.py b/plenum/server/plugin/did_plugin/config.py new file mode 100644 index 0000000000..6a6c484192 --- /dev/null +++ b/plenum/server/plugin/did_plugin/config.py @@ -0,0 +1,8 @@ +from plenum.common.constants import KeyValueStorageType + + +def get_config(config): + config.didPluginTransactionsFile = 'did_plugin_transactions' + config.didPluginStateStorage = KeyValueStorageType.Leveldb + config.didPluginStateDbName = 'did_plugin_state' + return config diff --git a/plenum/server/plugin/did_plugin/conftest.py b/plenum/server/plugin/did_plugin/conftest.py new file mode 100644 index 0000000000..4833d81e3b --- /dev/null +++ b/plenum/server/plugin/did_plugin/conftest.py @@ -0,0 +1,82 @@ +import importlib + +import pytest +from copy import deepcopy + +from plenum import setup_plugins, PLUGIN_LEDGER_IDS, PLUGIN_CLIENT_REQUEST_FIELDS +from plenum.common.pkg_util import update_module_vars +from plenum.server.plugin.did_plugin import AUCTION_LEDGER_ID +from plenum.server.plugin.did_plugin.main import integrate_plugin_in_node + + +def do_plugin_initialisation_for_tests(): + # The next imports and reloading are needed only in tests, since in + # production none of these modules would be loaded before plugins are + # setup (not initialised) + import plenum.server + import plenum.common + + importlib.reload(plenum.server.replica) + importlib.reload(plenum.server.consensus.view_change_trigger_service) + importlib.reload(plenum.server.consensus.view_change_service) + importlib.reload(plenum.server.consensus.view_change_storages) + importlib.reload(plenum.server.consensus.ordering_service) + importlib.reload(plenum.server.consensus.ordering_service_msg_validator) + importlib.reload(plenum.server.node) + importlib.reload(plenum.server.catchup.utils) + importlib.reload(plenum.server.catchup.catchup_rep_service) + importlib.reload(plenum.server.catchup.cons_proof_service) + importlib.reload(plenum.server.catchup.ledger_leecher_service) + importlib.reload(plenum.server.catchup.node_leecher_service) + importlib.reload(plenum.server.catchup.seeder_service) + importlib.reload(plenum.server.message_handlers) + importlib.reload(plenum.server.observer.observable) + importlib.reload(plenum.common.ledger_manager) + + +@pytest.fixture(scope="module") +def tconf(tconf, request): + global PLUGIN_LEDGER_IDS, PLUGIN_CLIENT_REQUEST_FIELDS + + orig_plugin_root = deepcopy(tconf.PLUGIN_ROOT) + orig_enabled_plugins = deepcopy(tconf.ENABLED_PLUGINS) + orig_plugin_ledger_ids = deepcopy(PLUGIN_LEDGER_IDS) + orig_plugin_client_req_fields = deepcopy(PLUGIN_CLIENT_REQUEST_FIELDS) + + update_module_vars('plenum.config', + **{ + 'PLUGIN_ROOT': 'plenum.test.plugin', + 'ENABLED_PLUGINS': ['demo_plugin', ], + }) + PLUGIN_LEDGER_IDS = {AUCTION_LEDGER_ID} + PLUGIN_CLIENT_REQUEST_FIELDS = {} + setup_plugins() + do_plugin_initialisation_for_tests() + + def reset(): + global PLUGIN_LEDGER_IDS, PLUGIN_CLIENT_REQUEST_FIELDS + update_module_vars('plenum.config', + **{ + 'PLUGIN_ROOT': orig_plugin_root, + 'ENABLED_PLUGINS': orig_enabled_plugins, + }) + PLUGIN_LEDGER_IDS = orig_plugin_ledger_ids + PLUGIN_CLIENT_REQUEST_FIELDS = orig_plugin_client_req_fields + setup_plugins() + + request.addfinalizer(reset) + return tconf + + +@pytest.fixture(scope="module") +def do_post_node_creation(): + # Integrate plugin into each node. + def _post_node_creation(node): + integrate_plugin_in_node(node) + + return _post_node_creation + + +@pytest.fixture(scope="module") +def txn_pool_node_set_post_creation(tconf, do_post_node_creation, txnPoolNodeSet): + return txnPoolNodeSet diff --git a/plenum/server/plugin/did_plugin/constants.py b/plenum/server/plugin/did_plugin/constants.py new file mode 100644 index 0000000000..4df2d2de74 --- /dev/null +++ b/plenum/server/plugin/did_plugin/constants.py @@ -0,0 +1,15 @@ +from plenum.server.plugin.did_plugin import DemoTransactions + +DID_PLUGIN_LEDGER_ID = 2023 + +CREATE_DID = DemoTransactions.CREATE_DID.value +CREATE_NETWORK_DID = DemoTransactions.CREATE_NETWORK_DID.value +FETCH_DID = DemoTransactions.FETCH_DID.value +UPDATE_DID = DemoTransactions.UPDATE_DID.value +UPDATE_NETWORK_DID = DemoTransactions.UPDATE_NETWORK_DID.value +DEACTIVATE_DID = DemoTransactions.DEACTIVATE_DID.value +OUDID = DemoTransactions.OUDID.value +SDDID = DemoTransactions.SDDID.value + +AMOUNT = "amount" +ID = "id" diff --git a/plenum/server/plugin/did_plugin/helper.py b/plenum/server/plugin/did_plugin/helper.py new file mode 100644 index 0000000000..2f90414335 --- /dev/null +++ b/plenum/server/plugin/did_plugin/helper.py @@ -0,0 +1,67 @@ +from plenum.common.config_helper import PNodeConfigHelper +from plenum.test.test_node import ensure_node_disconnected, TestNode, checkNodesConnected, ensureElectionsDone + +from plenum.common.constants import TXN_TYPE, DATA +from plenum.test.helper import sdk_gen_request, sdk_sign_and_submit_req_obj, sdk_get_reply, sdk_get_and_check_replies +from plenum.server.plugin.did_plugin.constants import AUCTION_START, GET_AUCTION + + +def send_auction_txn(looper, + sdk_pool_handle, sdk_wallet_steward): + op = { + TXN_TYPE: AUCTION_START, + DATA: {'id': 'abc'} + } + return successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + +def send_get_auction_txn(looper, + sdk_pool_handle, sdk_wallet_steward): + op = { + TXN_TYPE: GET_AUCTION, + DATA: {'auction_id': 'id'} + } + return successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + +def successful_op(looper, op, sdk_wallet, sdk_pool_handle): + req_obj = sdk_gen_request(op, identifier=sdk_wallet[1]) + req = sdk_sign_and_submit_req_obj(looper, sdk_pool_handle, + sdk_wallet, req_obj) + return sdk_get_and_check_replies(looper, [req]) + + +def restart_nodes(looper, nodeSet, restart_set, tconf, tdir, allPluginsPath, + after_restart_timeout=None, start_one_by_one=True, wait_for_elections=True): + for node_to_stop in restart_set: + node_to_stop.cleanupOnStopping = True + node_to_stop.stop() + looper.removeProdable(node_to_stop) + + rest_nodes = [n for n in nodeSet if n not in restart_set] + for node_to_stop in restart_set: + ensure_node_disconnected(looper, node_to_stop, nodeSet, timeout=2) + + if after_restart_timeout: + looper.runFor(after_restart_timeout) + + for node_to_restart in restart_set.copy(): + config_helper = PNodeConfigHelper(node_to_restart.name, tconf, chroot=tdir) + restarted_node = TestNode(node_to_restart.name, config_helper=config_helper, config=tconf, + pluginPaths=allPluginsPath, ha=node_to_restart.nodestack.ha, + cliha=node_to_restart.clientstack.ha) + looper.add(restarted_node) + + idx = nodeSet.index(node_to_restart) + nodeSet[idx] = restarted_node + restart_set[idx] = restarted_node + + rest_nodes += [restarted_node] + if start_one_by_one: + looper.run(checkNodesConnected(rest_nodes)) + + if not start_one_by_one: + looper.run(checkNodesConnected(nodeSet)) + + if wait_for_elections: + ensureElectionsDone(looper=looper, nodes=nodeSet) diff --git a/plenum/server/plugin/did_plugin/main.py b/plenum/server/plugin/did_plugin/main.py new file mode 100644 index 0000000000..c8b27c7a63 --- /dev/null +++ b/plenum/server/plugin/did_plugin/main.py @@ -0,0 +1,64 @@ +from plenum.common.constants import DOMAIN_LEDGER_ID +from plenum.server.client_authn import CoreAuthNr +from plenum.server.plugin.did_plugin import DID_PLUGIN_LEDGER_ID +from plenum.server.plugin.did_plugin.batch_handlers.did_plugin_batch_handler import DIDBatchHandler +from plenum.server.plugin.did_plugin.config import get_config +from plenum.server.plugin.did_plugin.request_handlers.create_did_handler import CreateDIDHandler +from plenum.server.plugin.did_plugin.request_handlers.create_org_unit_id import CreateOUDIDHandler +from plenum.server.plugin.did_plugin.request_handlers.create_security_domain_did import CreateSDDIDHandler +# from plenum.server.plugin.did_plugin.request_handlers.create_network_did_haendler import CreateNetworkDIDHandler + +from plenum.server.plugin.did_plugin.request_handlers.fetch_did import FetchDIDHandler + +from plenum.server.plugin.did_plugin.storage import get_did_plugin_hash_store, \ + get_did_plugin_ledger, get_did_plugin_state + + +# @Q... what is node.. where is the definition... +def integrate_plugin_in_node(node): + node.config = get_config(node.config) + hash_store = get_did_plugin_hash_store(node.dataLocation) + ledger = get_did_plugin_ledger(node.dataLocation, + node.config.didPluginTransactionsFile, + hash_store, node.config) + state = get_did_plugin_state(node.dataLocation, + node.config.didPluginStateDbName, + node.config) + if DID_PLUGIN_LEDGER_ID not in node.ledger_ids: + node.ledger_ids.append(DID_PLUGIN_LEDGER_ID) + node.ledgerManager.addLedger(DID_PLUGIN_LEDGER_ID, + ledger, + postTxnAddedToLedgerClbk=node.postTxnFromCatchupAddedToLedger) + node.on_new_ledger_added(DID_PLUGIN_LEDGER_ID) + node.register_state(DID_PLUGIN_LEDGER_ID, state) + + did_dict = {} + node.write_manager.register_req_handler(CreateDIDHandler(node.db_manager, did_dict)) + node.write_manager.register_req_handler(CreateOUDIDHandler(node.db_manager, did_dict)) + node.write_manager.register_req_handler(CreateSDDIDHandler(node.db_manager, did_dict)) + # node.write_manager.register_req_handler(CreateSDDIDHandler(node.db_manager, did_dict)) + + # node.write_manager.register_req_handler(AuctionEndHandler(node.db_manager, did_dict)) + # node.write_manager.register_req_handler(PlaceBidHandler(node.db_manager, did_dict)) + # node.read_manager.register_req_handler(GetBalHandler(node.db_manager)) + node.read_manager.register_req_handler(FetchDIDHandler(node.db_manager)) + # FIXME: find a generic way of registering DBs + node.db_manager.register_new_database(lid=DID_PLUGIN_LEDGER_ID, + ledger=ledger, + state=state) + node.write_manager.register_batch_handler(DIDBatchHandler(node.db_manager), + ledger_id=DID_PLUGIN_LEDGER_ID, + add_to_begin=True) + node.write_manager.register_batch_handler(node.write_manager.node_reg_handler, + ledger_id=DID_PLUGIN_LEDGER_ID) + node.write_manager.register_batch_handler(node.write_manager.primary_reg_handler, + ledger_id=DID_PLUGIN_LEDGER_ID) + node.write_manager.register_batch_handler(node.write_manager.audit_b_handler, + ledger_id=DID_PLUGIN_LEDGER_ID) + + did_plugin_authnr = CoreAuthNr(node.write_manager.txn_types, + node.read_manager.txn_types, + node.action_manager.txn_types, + node.states[DOMAIN_LEDGER_ID]) + node.clientAuthNr.register_authenticator(did_plugin_authnr) + return node diff --git a/plenum/server/plugin/did_plugin/request_handlers/__init__.py b/plenum/server/plugin/did_plugin/request_handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plenum/server/plugin/did_plugin/request_handlers/abstract_did_req_handler.py b/plenum/server/plugin/did_plugin/request_handlers/abstract_did_req_handler.py new file mode 100644 index 0000000000..95134cda8f --- /dev/null +++ b/plenum/server/plugin/did_plugin/request_handlers/abstract_did_req_handler.py @@ -0,0 +1,51 @@ +from abc import ABCMeta +from typing import Optional + +from common.serializers.json_serializer import JsonSerializer +from plenum.common.constants import TXN_TYPE, DATA +from plenum.common.exceptions import InvalidClientRequest, \ + UnauthorizedClientRequest +from plenum.common.request import Request +from plenum.common.txn_util import get_payload_data +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler +from plenum.server.plugin.did_plugin.constants import DID_PLUGIN_LEDGER_ID + + +class AbstractDIDReqHandler(WriteRequestHandler, metaclass=ABCMeta): + + @property + def did_dict(self): + return self._did_dict + + def __init__(self, database_manager: DatabaseManager, txn_type, did_dict: dict): + super().__init__(database_manager, txn_type, DID_PLUGIN_LEDGER_ID) + self._did_dict = did_dict + + def static_validation(self, request: Request): + """ + Ensure that the request payload has a 'data' field which is of type dict. + """ + self._validate_request_type(request) + identifier, req_id, operation = request.identifier, request.reqId, request.operation + data = operation.get(DATA) + if not isinstance(data, dict): + msg = '{} attribute is missing or not in proper format'.format(DATA) + raise InvalidClientRequest(identifier, req_id, msg) + + def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]): + """ + Ensure that the 'data' dict has a field 'id' + """ + self._validate_request_type(request) + operation = request.operation + data = operation.get(DATA) + if data['id'] not in self.did_dict: + raise UnauthorizedClientRequest(request.identifier, + request.reqId, + 'additional_dynamic_validation failed in AbstractDIDReqHandler') + + # def update_state(self, txn, prev_result, request, is_committed=False): + # data = get_payload_data(txn).get(DATA) + # for k, v in data.items(): + # self.state.set(k.encode(), JsonSerializer.dumps(v)) diff --git a/plenum/server/plugin/did_plugin/request_handlers/create_did_handler.py b/plenum/server/plugin/did_plugin/request_handlers/create_did_handler.py new file mode 100644 index 0000000000..35c1838f7a --- /dev/null +++ b/plenum/server/plugin/did_plugin/request_handlers/create_did_handler.py @@ -0,0 +1,132 @@ +import libnacl.sign + +from typing import Optional +import json + +from plenum.common.constants import DATA +from plenum.common.request import Request +from common.serializers.serialization import domain_state_serializer +from plenum.common.exceptions import InvalidClientRequest, MissingSignature, InvalidSignature + +from plenum.server.database_manager import DatabaseManager +from plenum.server.plugin.did_plugin.constants import CREATE_DID +from plenum.server.plugin.did_plugin.request_handlers.abstract_did_req_handler import AbstractDIDReqHandler +from plenum.server.plugin.did_plugin.common import DID, libnacl_validate + +from plenum.common.txn_util import get_payload_data, get_from, \ + get_seq_no, get_txn_time, get_request_data + +import libnacl +import libnacl.encode + +""" +{ + "identifier": "EbP4aYNeTHL6q385GuVpRV", + "operation": { + "dest": "Vzfdscz6YG6n1EuNJV4ob1", + "type": "20220", + "data": { + "DIDDocument": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "did:iin:iin123:shippingcompany", + "verificationMethod": [ + { + "id": "did:iin:iin123:shippingcompany#key-1", + "type": "Ed25519VerificationKey2020", + "controller": "did:example:123456789abcdefghi", + "publicKeyBase64": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ], + "authentication": [ + "did:iin:iin123:shippingcompany#keys-1", + { + "id": "did:iin:iin123:shippingcompany#keys-2", + "type": "Ed25519VerificationKey2020", + "controller": "did:shippingcompany", + "publicKeyBase64": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ] + }, + "signature": { + "verificationMethod": "did:iin:iin123:shippingcompany#keys-1", + "sigbase64": "sdfsdfsdf" + } + }, + "verkey": "~HFPBKb7S7ocrTzxakNbcao" + }, + "protocolVersion": 2, + "reqId": 1704278802665233400 +} + +""" + +class CreateDIDRequest: + did: DID = None + did_str = None + signature = None + + + def __init__(self, request_dict: str) -> None: + self.did_str = json.dumps(request_dict["DIDDocument"]) + self.did = DID(self.did_str) + self.signature = request_dict["signature"] + + def authenticate(self): + # Get authentication method + auth_method = self.did.fetch_authentication_method(self.signature["verificationMethod"]) + + if not auth_method: + raise MissingSignature("Authentication verification method not found in DIDDocument.") + + if auth_method["type"] == "libnacl": + # validate signature + # TODO: Json serialization is not faithful. Use ordered collections isntead. + originalhash = libnacl.crypto_hash_sha256(self.did_str) + libnacl_validate(auth_method["publicKeyBase64"], self.signature["sigbase64"], originalhash) + + # TODO: Add more authentication methods / some standard + else: + raise InvalidSignature("Unknown signature type: ", auth_method["type"]) + + +class CreateDIDHandler(AbstractDIDReqHandler): + + def __init__(self, database_manager: DatabaseManager, did_dict: dict): + super().__init__(database_manager, CREATE_DID, did_dict) + + def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]): + + operation = request.operation + create_did_request_dict = operation.get(DATA) + + # parse create did request + try: + create_did_request = CreateDIDRequest(create_did_request_dict) + except: + raise InvalidClientRequest(request.identifier, request.reqId, "Malformed CREATE_DID request.") + + # TODO Check if the did uri corresponds to this iin or not. + + # Check if did already in this iin or not. + # serialized_did = self.state.get(create_did_request.did.id, isCommitted=False) + # if serialized_did: + # raise InvalidClientRequest(request.identifier, request.reqId, "DID already exists.") + + # Authenticate + create_did_request.authenticate() + + + + def update_state(self, txn, prev_result, request, is_committed=False): + data = get_payload_data(txn).get(DATA) + create_did_request = CreateDIDRequest(data) + + self.did_dict[create_did_request.did.id] = create_did_request.did_str + key = create_did_request.did.id + val = self.did_dict[create_did_request.did.id] + print("Setting state:", key, val) + self.state.set(key.encode(), val) + return val diff --git a/plenum/server/plugin/did_plugin/request_handlers/create_network_did_handler.py b/plenum/server/plugin/did_plugin/request_handlers/create_network_did_handler.py new file mode 100644 index 0000000000..ab4a83b113 --- /dev/null +++ b/plenum/server/plugin/did_plugin/request_handlers/create_network_did_handler.py @@ -0,0 +1,200 @@ +import libnacl.sign + +from typing import Optional +import json + +from plenum.common.constants import DATA +from plenum.common.request import Request +from common.serializers.serialization import domain_state_serializer +from plenum.common.exceptions import InvalidClientRequest, MissingSignature, InvalidSignature + +from plenum.server.database_manager import DatabaseManager +from plenum.server.plugin.did_plugin.constants import CREATE_NETWORK_DID +from plenum.server.plugin.did_plugin.request_handlers.abstract_did_req_handler import AbstractDIDReqHandler +from plenum.server.plugin.did_plugin.common import DID, NetworkDID, did_id_from_url, libnacl_validate + + +from plenum.common.txn_util import get_payload_data, get_from, \ + get_seq_no, get_txn_time, get_request_data + +import libnacl +import libnacl.encode + +""" +CreateNetworkDID request structure: + +{ + "NetworkDIDDocument": { + "id": "did:iin::", + "networkParticipants": [ + "did:iin::", + "did:iin::", + "did:iin::" + ], + "verificationMethod": [{ + "id": "did:iin::#multisig", + "type": "GroupMultiSig", + "controller": "did:iin::", + "multisigKeys": [ + "did:iin::#key1", + "did:iin::#key3", + "did:iin::#key1" + ], + "updatePolicy": { + "id": "did:iin::#updatepolicy", + "controller": "did:iin::", + "type": "VerifiableCondition2021", + "conditionAnd": [{ + "id": "did:iin::#updatepolicy-1", + "controller": "did:iin::", + "type": "VerifiableCondition2021", + "conditionOr": ["did:iin::#key1", + "did:iin::#key3" + ] + }, + "did:iin::#key1" + ] + } + }, + + { + "id": "did:iin::#fabriccerts", + "type": "DataplaneCredentials", + "controller": "did:iin::", + "FabricCredentials": { + "did:iin::": "Certificate3_Hash", + "did:iin::": "Certificate2_Hash", + "did:iin::": "Certificate3_Hash" + } + } + ], + "authentication": [ + "did:iin::#multisig" + ], + "networkGatewayEndpoints": [{ + "hostname": "10.0.0.8", + "port": "8888" + }, + { + "hostname": "10.0.0.9", + "port": "8888" + } + + ] + }, + "signatures": { + "did:iin::": "...", + "did:iin::": "...", + "did:iin::": "..." + } +} + +""" + +class CreateNetworkDIDRequest: + did: NetworkDID = None + did_str = None + signatures = None + this_indy_state = None + + def __init__(self, request_dict: str, indy_state) -> None: + self.did_str = json.dumps(request_dict["NetworkDIDDocument"]) + self.did = NetworkDID(self.did_str) + self.signatures = request_dict["signatures"] + self.this_indy_state = indy_state + + def fetch_party_key_from_auth_method(self, party_did_id, auth_method): + for candidate_key_url in auth_method["multisigKeys"]: + base_url = did_id_from_url(candidate_key_url) + if base_url == party_did_id: + return candidate_key_url + + def fetch_party_verification_method(self, party_key_url): + party_did_id = did_id_from_url(party_key_url) + # Fetch party did + # TODO: if did is in some other iin network + + # 1 did:iin:someotheriin1:sdfsdfsd + # did:iin:somethingelse:asdasd + + # If did is in the same indy iin network + serialized_party_did = self.this_indy_state.get(party_did_id, isCommitted=True) + if not serialized_party_did: + raise "Could not resolve did " + party_did_id + + party_did = domain_state_serializer.deserialize(serialized_party_did) + party_did = DID(party_did) + party_authentication_method = party_did.fetch_authentication_method(party_key_url) + return party_authentication_method + + def authenticate(self): + # Get any one authentication method of type GroupMultiSig + auth_method = self.did.fetch_authentication_method() + + if not auth_method: + raise MissingSignature("Authentication verification method not found in NetworkDIDDocument.") + + # Iterate of each participant + for party_did_id in self.did.network_participants: + # Fetch the key url from auth_method + party_key_url = self.fetch_party_key_from_auth_method(auth_method, party_did_id) + + # Fetch verification key of the party + party_verification_method = self.fetch_party_verification_method(party_key_url) + + # Validate signature of the party + if party_verification_method["type"] == "libnacl": + # validate signature + # TODO: Json serialization is not faithful. Use ordered collections isntead. + originalhash = libnacl.crypto_hash_sha256(self.did_str) + libnacl_validate(party_verification_method["publicKeyBase64"], self.signatures[party_did_id], originalhash) + + # TODO: Add more authentication methods / some standard + else: + raise InvalidSignature("Unknown signature type: ", auth_method["type"]) + + if auth_method["type"] == "libnacl": + # validate signature + self._libnacl_validate(auth_method["publicKeyBase64"], self.signature["sigbase64"]) + # TODO: Add more authentication methods / some standard + else: + raise InvalidSignature("Unknown signature type: ", auth_method["type"]) + +class CreateNetworkDIDHandler(AbstractDIDReqHandler): + + def __init__(self, database_manager: DatabaseManager, did_dict: dict): + super().__init__(database_manager, CREATE_NETWORK_DID, did_dict) + + def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]): + + operation = request.operation + create_network_did_request_dict = operation.get(DATA) + + # parse create did request + try: + create_network_did_request = CreateNetworkDIDRequest(create_network_did_request_dict, self.state) + except: + raise InvalidClientRequest(request.identifier, request.reqId, "Malformed CREATE_NETWORK_DID request.") + + # TODO Check if the did uri corresponds to this iin or not. + + # Check if did already in this iin or not. + # serialized_did = self.state.get(create_network_did_request.did.id, isCommitted=False) + # if serialized_did: + # raise InvalidClientRequest(request.identifier, request.reqId, "DID already exists.") + + # Authenticate + create_network_did_request.authenticate() + + + + def update_state(self, txn, prev_result, request, is_committed=False): + data = get_payload_data(txn).get(DATA) + create_network_did_request = CreateNetworkDIDRequest(data) + + self.did_dict[create_network_did_request.did.id] = create_network_did_request.did_str + key = create_network_did_request.did.id + val = self.did_dict[create_network_did_request.did.id] + print("Setting state:", key, val) + self.state.set(key.encode(), val) + return val diff --git a/plenum/server/plugin/did_plugin/request_handlers/create_org_unit_id.py b/plenum/server/plugin/did_plugin/request_handlers/create_org_unit_id.py new file mode 100644 index 0000000000..9a0d8f42d0 --- /dev/null +++ b/plenum/server/plugin/did_plugin/request_handlers/create_org_unit_id.py @@ -0,0 +1,129 @@ +import libnacl.sign +import nacl +from typing import Optional +import json +from plenum.common.constants import DATA +from plenum.common.request import Request +from common.serializers.serialization import domain_state_serializer +from plenum.common.exceptions import InvalidClientRequest, MissingSignature, InvalidSignature +from plenum.server.database_manager import DatabaseManager +from plenum.server.plugin.did_plugin.constants import OUDID +from plenum.server.plugin.did_plugin.request_handlers.abstract_did_req_handler import AbstractDIDReqHandler +from plenum.server.plugin.did_plugin.common import DID, libnacl_validate +from plenum.common.txn_util import get_payload_data, get_from, \ + get_seq_no, get_txn_time, get_request_data + +import libnacl + +import libnacl.encode +print("hello1") + +""" +DID identifier (globally unique)::> Stencil: did:: + Ex.: did:exampleiin:org1 + +{ + "id": "did:exampleiin:org1", + "verificationMethod": [{ + "id": "did:exampleiin:org1#key1", + "type": "Ed25519VerificationKey2020", + "controller": "did:exampleiin:org1", + "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + }], + + "authentication": ["did:exampleiin:org1#key1"] +} + + +================================ +| CreateDID request structure: | +================================ + +================================================================================= +|| { || +|| "DIDDocument": { "controller || +|| "id": "did::", || +|| "verificationMethod": [{ || +|| "id": "did::", || +|| "type": "Ed25519VerificationKey2020", || +|| "controller": "did::", || +|| "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" || <=================??????======= +|| }], || +|| || +|| "authentication": ["did::"] || +|| }, || +|| "signature": {"did:iin::": "..."} || +|| } || +================================================================================= + + +""" + +class CreateOUDIDRequest: + did: DID = None + did_str = None + signature = None + + def __init__(self, request_dict: str) -> None: + self.did_str = json.dumps(request_dict["DIDDocument"]) + self.did = DID(self.did_str) + self.signature = request_dict["signature"] + + def authenticate(self): + # Get authentication method + print(self.signature) + auth_method = self.did.fetch_authentication_method(self.signature["verificationMethod"]) + print(f"=========={self.signature}==========") + print(auth_method) + print(auth_method["type"]) + if not auth_method: + raise MissingSignature("Authentication verification method not found in DIDDocument.") + if auth_method["type"] == "libnacl": + # validate signature + # TODO: Json serialization is not faithful. Use ordered collections isntead. + originalhash = libnacl.crypto_hash_sha256(self.did_str) + # TAG::> + libnacl_validate(auth_method["publicKeyMultibase"], self.signature["sigbase64"], originalhash) + # TODO: Add more authentication methods / some standard + else: + raise InvalidSignature("Unknown signature type: ", auth_method["type"]) + + + +class CreateOUDIDHandler(AbstractDIDReqHandler): + + def __init__(self, database_manager: DatabaseManager, did_dict: dict): + super().__init__(database_manager, OUDID, did_dict) + + def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]): + + operation = request.operation + create_did_request_dict = operation.get(DATA) + + # parse create did request + try: + create_did_request = CreateOUDIDRequest(create_did_request_dict) + except: + raise InvalidClientRequest(request.identifier, request.reqId, "Malformed OUDID request.") + + # TODO Check if the did uri corresponds to this iin or not. + + # Check if did already in this iin or not. + # serialized_did = self.state.get(create_did_request.did.id, isCommitted=False) + # if serialized_did: + # raise InvalidClientRequest(request.identifier, request.reqId, "DID already exists.") + + # Authenticate + create_did_request.authenticate() + + + def update_state(self, txn, prev_result, request, is_committed=False): + data = get_payload_data(txn).get(DATA) + create_did_request = CreateOUDIDRequest(data) + + self.did_dict[create_did_request.did.id] = create_did_request.did_str + key = create_did_request.did.id + val = self.did_dict[create_did_request.did.id] + print("Setting state:", key, val) + self.state.set(key.encode(), val) + return val \ No newline at end of file diff --git a/plenum/server/plugin/did_plugin/request_handlers/create_security_domain_did.py b/plenum/server/plugin/did_plugin/request_handlers/create_security_domain_did.py new file mode 100644 index 0000000000..0c0b6f099b --- /dev/null +++ b/plenum/server/plugin/did_plugin/request_handlers/create_security_domain_did.py @@ -0,0 +1,115 @@ +import libnacl.sign + +from typing import Optional +import json + +from plenum.common.constants import DATA +from plenum.common.request import Request +from common.serializers.serialization import domain_state_serializer +from plenum.common.exceptions import InvalidClientRequest, MissingSignature, InvalidSignature + +from plenum.server.database_manager import DatabaseManager +from plenum.server.plugin.did_plugin.constants import SDDID +from plenum.server.plugin.did_plugin.request_handlers.abstract_did_req_handler import AbstractDIDReqHandler +from plenum.server.plugin.did_plugin.common import DID, NetworkDID, did_id_from_url, libnacl_validate, libnacl_validate2 + + +from plenum.common.txn_util import get_payload_data, get_from, \ + get_seq_no, get_txn_time, get_request_data + +import libnacl +import libnacl.encode + +class CreateSDDIDRequest: + did: NetworkDID = None + did_str = None + signatures = None + this_indy_state = None + def __init__(self, request_dict: str, indy_state) -> None: + self.did_str = json.dumps(request_dict["DIDDocument"]) + self.did = NetworkDID(self.did_str) + self.signatures = request_dict["signatures"] + self.this_indy_state = indy_state + + def fetch_party_key_from_auth_method(self, party_did_id, auth_method): + for candidate_key_url in auth_method["multisigKeys"]: + base_url = did_id_from_url(candidate_key_url) + if base_url == party_did_id: + return candidate_key_url + + def fetch_party_verification_method(self, party_key_url): + print("hello3") + party_did_id = did_id_from_url(party_key_url) + # Fetch party did + # TODO: if did is in some other iin network + # Will handle later... + + # If did is in the same indy iin network + serialized_party_did = self.this_indy_state.get(party_did_id) + # if not serialized_party_did: + # raise "Could not resolve did " + party_did_id + print(serialized_party_did) + party_did = domain_state_serializer.deserialize(serialized_party_did) + party_did = DID(party_did) + party_authentication_method = party_did.fetch_authentication_method(party_key_url) + return party_authentication_method + + + def authenticate(self): + # Get any one authentication method of type GroupMultiSig + auth_method = self.did.fetch_authentication_method() + + if not auth_method: + raise MissingSignature("Authentication verification method not found in SDDIDDocument.") + # Iterate of each participant + for party_did_id in self.did.network_participants: + # Fetch the key url from auth_method + party_key_url = self.fetch_party_key_from_auth_method(party_did_id, auth_method) + # Fetch verification key of the party + party_verification_method = self.fetch_party_verification_method(party_key_url) + # Validate signature of the party + if party_verification_method["type"] == "libnacl": + # validate signature + # TODO: Json serialization is not faithful. Use ordered collections isntead. + originalhash = libnacl.crypto_hash_sha256(self.did_str) + libnacl_validate(party_verification_method["publicKeyBase64"], self.signatures[party_did_id], originalhash) + + # TODO: Add more authentication methods / some standard + else: + raise InvalidSignature("Unknown signature type: ", auth_method["type"]) + +class CreateSDDIDHandler(AbstractDIDReqHandler): + + def __init__(self, database_manager: DatabaseManager, did_dict: dict): + super().__init__(database_manager, SDDID, did_dict) + + def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]): + operation = request.operation + create_network_did_request_dict = operation.get(DATA) + # parse create did request + # try: + create_network_did_request = CreateSDDIDRequest(create_network_did_request_dict, self.state) + # except: + # raise InvalidClientRequest(request.identifier, request.reqId, "Malformed CREATE_NETWORK_DID request.") + + # TODO Check if the did uri corresponds to this iin or not. + + # Check if did already in this iin or not. + serialized_did = self.state.get(create_network_did_request.did.id, isCommitted=True) + if serialized_did: + raise InvalidClientRequest(request.identifier, request.reqId, "DID already exists.") + + # Authenticate + create_network_did_request.authenticate() + + + def update_state(self, txn, prev_result, request, is_committed=False): + data = get_payload_data(txn).get(DATA) + create_network_did_request = CreateSDDIDRequest(data, self.state) + + self.did_dict[create_network_did_request.did.id] = create_network_did_request.did_str + key = create_network_did_request.did.id + val = self.did_dict[create_network_did_request.did.id] + print("Setting state:", key, val) + self.state.set(key.encode(), val) + return val diff --git a/plenum/server/plugin/did_plugin/request_handlers/fetch_did.py b/plenum/server/plugin/did_plugin/request_handlers/fetch_did.py new file mode 100644 index 0000000000..1f22e71272 --- /dev/null +++ b/plenum/server/plugin/did_plugin/request_handlers/fetch_did.py @@ -0,0 +1,31 @@ +from plenum.common.constants import DATA + +from common.serializers.json_serializer import JsonSerializer +from plenum.common.request import Request +from plenum.common.types import f +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.read_request_handler import ReadRequestHandler +from plenum.server.plugin.did_plugin import DID_PLUGIN_LEDGER_ID +from plenum.server.plugin.did_plugin.constants import FETCH_DID + + +class FetchDIDHandler(ReadRequestHandler): + + def __init__(self, database_manager: DatabaseManager): + super().__init__(database_manager, FETCH_DID, DID_PLUGIN_LEDGER_ID) + + def static_validation(self, request: Request): + pass + + def get_result(self, request: Request): + did = request.operation.get(DATA).get("id").encode() + serialized_did = self.state.get(did, isCommitted=True) + did_data, proof = self._get_value_from_state(did, with_proof=True) + + did = JsonSerializer().deserialize(serialized_did) if serialized_did else None + + return {**request.operation, **{ + f.IDENTIFIER.nm: request.identifier, + f.REQ_ID.nm: request.reqId, + "did": did + }} diff --git a/plenum/server/plugin/did_plugin/storage.py b/plenum/server/plugin/did_plugin/storage.py new file mode 100644 index 0000000000..918ab5b123 --- /dev/null +++ b/plenum/server/plugin/did_plugin/storage.py @@ -0,0 +1,24 @@ +from ledger.compact_merkle_tree import CompactMerkleTree +from plenum.common.ledger import Ledger +from plenum.persistence.db_hash_store import DbHashStore +from state.pruning_state import PruningState +from storage.helper import initKeyValueStorage +from plenum.common.constants import HS_LEVELDB + + +def get_did_plugin_hash_store(data_dir): + return DbHashStore(dataDir=data_dir, + fileNamePrefix='did_plugin', + db_type=HS_LEVELDB) + + +def get_did_plugin_ledger(data_dir, name, hash_store, config): + return Ledger(CompactMerkleTree(hashStore=hash_store), + dataDir=data_dir, + fileName=name, + ensureDurability=config.EnsureLedgerDurability) + + +def get_did_plugin_state(data_dir, name, config): + return PruningState(initKeyValueStorage( + config.didPluginStateStorage, data_dir, name)) diff --git a/plenum/server/plugin/did_plugin/test_catchup.py b/plenum/server/plugin/did_plugin/test_catchup.py new file mode 100644 index 0000000000..c9ef2e5efe --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_catchup.py @@ -0,0 +1,74 @@ +import pytest + +from plenum.common.constants import TXN_TYPE, DATA +from plenum.test.node_catchup.helper import waitNodeDataInequality, \ + waitNodeDataEquality +from plenum.server.plugin.did_plugin import AUCTION_LEDGER_ID + +from plenum.server.plugin.did_plugin.constants import AUCTION_END, PLACE_BID, \ + AMOUNT, AUCTION_START +from plenum.server.plugin.did_plugin.helper import successful_op +from plenum.server.plugin.did_plugin.test_plugin_request_handling import \ + some_requests +from plenum.test.pool_transactions.helper import \ + disconnect_node_and_ensure_disconnected, reconnect_node_and_ensure_connected + + +def test_new_node_catchup_plugin_ledger(txn_pool_node_set_post_creation, looper, some_requests, + sdk_new_node_caught_up): + """ + A new node catches up the demo plugin's ledger too + """ + assert len(sdk_new_node_caught_up.getLedger(AUCTION_LEDGER_ID)) > 0 + for node in txn_pool_node_set_post_creation[:-1]: + assert len(sdk_new_node_caught_up.getLedger(AUCTION_LEDGER_ID)) == \ + len(node.getLedger(AUCTION_LEDGER_ID)) + + +def some_demo_txns(looper, sdk_wallet_steward, sdk_pool_handle): + id = 'xyz' + ops = [ + { + TXN_TYPE: AUCTION_START, + DATA: {'id': id} + }, + { + TXN_TYPE: PLACE_BID, + DATA: {'id': id, AMOUNT: 20} + }, + { + TXN_TYPE: PLACE_BID, + DATA: {'id': id, AMOUNT: 40} + }, + { + TXN_TYPE: AUCTION_END, + DATA: {'id': id} + } + ] + + for op in ops: + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + +@pytest.mark.skip(reason="INDY-1297. Node does not catch up on reconnection anymore.") +def test_disconnected_node_catchup_plugin_ledger_txns(looper, + txnPoolNodeSet, + sdk_wallet_client, + sdk_pool_handle, + sdk_new_node_caught_up): + """ + A node gets disconnected, a few config ledger txns happen, + the disconnected node comes back up and catches up the config ledger + """ + new_node = sdk_new_node_caught_up + disconnect_node_and_ensure_disconnected( + looper, txnPoolNodeSet, new_node, stopNode=False) + + # Do some demo txns; + some_demo_txns(looper, sdk_wallet_client, sdk_pool_handle) + + # Make sure new node got out of sync + waitNodeDataInequality(looper, new_node, *txnPoolNodeSet[:-1]) + + reconnect_node_and_ensure_connected(looper, txnPoolNodeSet, new_node) + waitNodeDataEquality(looper, new_node, *txnPoolNodeSet[:-1]) diff --git a/plenum/server/plugin/did_plugin/test_freshness.py b/plenum/server/plugin/did_plugin/test_freshness.py new file mode 100644 index 0000000000..eee5024951 --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_freshness.py @@ -0,0 +1,37 @@ +import pytest + +from plenum.test.freshness.helper import get_multi_sig_values_for_all_nodes, \ + check_updated_bls_multi_sig_for_ledger, check_freshness_updated_for_ledger +from plenum.test.helper import freshness +from plenum.server.plugin.did_plugin import AUCTION_LEDGER_ID +from plenum.server.plugin.did_plugin.helper import send_auction_txn +from stp_core.loop.eventually import eventually + +FRESHNESS_TIMEOUT = 5 + + +@pytest.fixture(scope="module") +def tconf(tconf): + with freshness(tconf, enabled=True, timeout=FRESHNESS_TIMEOUT): + yield tconf + + +def test_update_bls_multi_sig_for_auction_ledger_by_timeout(looper, tconf, txnPoolNodeSet, + sdk_pool_handle, sdk_wallet_steward): + # 1. Update auction ledger + send_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward) + + # 2. Wait for the first freshness update + looper.run(eventually( + check_freshness_updated_for_ledger, txnPoolNodeSet, AUCTION_LEDGER_ID, + timeout=3 * FRESHNESS_TIMEOUT) + ) + + # 3. Wait for the second freshness update + bls_multi_sigs_after_first_update = get_multi_sig_values_for_all_nodes(txnPoolNodeSet, AUCTION_LEDGER_ID) + looper.run(eventually(check_updated_bls_multi_sig_for_ledger, + txnPoolNodeSet, AUCTION_LEDGER_ID, + bls_multi_sigs_after_first_update, + FRESHNESS_TIMEOUT, + timeout=FRESHNESS_TIMEOUT + 5 + )) diff --git a/plenum/server/plugin/did_plugin/test_freshness_during_ordering.py b/plenum/server/plugin/did_plugin/test_freshness_during_ordering.py new file mode 100644 index 0000000000..c2c25b282d --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_freshness_during_ordering.py @@ -0,0 +1,32 @@ +import pytest + +from plenum.common.constants import DOMAIN_LEDGER_ID +from plenum.test.freshness.helper import check_update_bls_multi_sig_during_ordering +from plenum.test.helper import freshness +from plenum.server.plugin.did_plugin import AUCTION_LEDGER_ID +from plenum.server.plugin.did_plugin.helper import send_auction_txn + +FRESHNESS_TIMEOUT = 10 + + +@pytest.fixture(scope="module") +def tconf(tconf): + with freshness(tconf, enabled=True, timeout=FRESHNESS_TIMEOUT): + yield tconf + + +def test_update_bls_multi_sig_when_auction_ledger_orders(looper, tconf, txnPoolNodeSet, + sdk_pool_handle, + sdk_wallet_steward): + # Update auction ledger so that its state root is different from config ledger + for node in txnPoolNodeSet: + node.states[AUCTION_LEDGER_ID].set(b'some_key', b'some_value') + + def send_txn(): + send_auction_txn(looper, + sdk_pool_handle, sdk_wallet_steward) + + check_update_bls_multi_sig_during_ordering(looper, txnPoolNodeSet, + send_txn, + FRESHNESS_TIMEOUT, + AUCTION_LEDGER_ID, DOMAIN_LEDGER_ID) diff --git a/plenum/server/plugin/did_plugin/test_frozen_ledgers.py b/plenum/server/plugin/did_plugin/test_frozen_ledgers.py new file mode 100644 index 0000000000..c88f06990a --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_frozen_ledgers.py @@ -0,0 +1,74 @@ +import pytest +from plenum.server.plugin.did_plugin.constants import AUCTION_LEDGER_ID + +from plenum.test.freeze_ledgers.helper import sdk_send_freeze_ledgers, sdk_get_frozen_ledgers +from plenum.test.helper import freshness + +from plenum.common.constants import DATA +from plenum.test.freshness.helper import check_freshness_updated_for_ledger + + +from stp_core.loop.eventually import eventually + +FRESHNESS_TIMEOUT = 5 + + +@pytest.fixture(scope="module") +def tconf(tconf): + with freshness(tconf, enabled=True, timeout=FRESHNESS_TIMEOUT): + yield tconf + + +def test_send_freeze_ledgers(looper, txnPoolNodeSet, sdk_pool_handle, sdk_wallet_trustee): + ledger_to_remove = AUCTION_LEDGER_ID + + # check that the config state doesn't contain frozen ledgers records + result = sdk_get_frozen_ledgers(looper, sdk_pool_handle, + sdk_wallet_trustee)[1]["result"][DATA] + assert result is None + + # add to the config state a frozen ledgers record with an empty list + sdk_send_freeze_ledgers( + looper, sdk_pool_handle, + [sdk_wallet_trustee], + [] + ) + + # check that the config state contains a frozen ledgers record with an empty list + result = sdk_get_frozen_ledgers(looper, sdk_pool_handle, + sdk_wallet_trustee)[1]["result"][DATA] + assert len(result) == 0 + + # add to the config state a frozen ledgers record with AUCTION ledger + looper.run(eventually( + check_freshness_updated_for_ledger, txnPoolNodeSet, ledger_to_remove, + timeout=3 * FRESHNESS_TIMEOUT) + ) + sdk_send_freeze_ledgers( + looper, sdk_pool_handle, + [sdk_wallet_trustee], + [ledger_to_remove] + ) + + # check that the config state contains a frozen ledgers record with AUCTION ledger + result = sdk_get_frozen_ledgers(looper, sdk_pool_handle, + sdk_wallet_trustee)[1]["result"][DATA] + assert len(result) == 1 + assert result[str(ledger_to_remove)]["state"] + assert result[str(ledger_to_remove)]["ledger"] + assert result[str(ledger_to_remove)]["seq_no"] >= 0 + + # add to the config state a frozen ledgers record with an empty list + sdk_send_freeze_ledgers( + looper, sdk_pool_handle, + [sdk_wallet_trustee], + [] + ) + + # check that the frozen ledgers list from the state wasn't cleared by the transaction with empty ledgers' list + result = sdk_get_frozen_ledgers(looper, sdk_pool_handle, + sdk_wallet_trustee)[1]["result"][DATA] + assert len(result) == 1 + assert result[str(ledger_to_remove)]["state"] + assert result[str(ledger_to_remove)]["ledger"] + assert result[str(ledger_to_remove)]["seq_no"] >= 0 diff --git a/plenum/server/plugin/did_plugin/test_plugin_basic.py b/plenum/server/plugin/did_plugin/test_plugin_basic.py new file mode 100644 index 0000000000..885c983e44 --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_plugin_basic.py @@ -0,0 +1,50 @@ +import pytest + +from plenum.common.exceptions import RequestNackedException +from plenum.test.helper import sdk_gen_request, \ + sdk_sign_and_submit_req_obj, sdk_get_reply, sdk_sign_request_objects, sdk_send_signed_requests, \ + sdk_get_and_check_replies +from plenum.common.constants import TXN_TYPE, DATA +from plenum.common.util import randomString +from plenum.server.plugin.did_plugin import AUCTION_LEDGER_ID, dummy_field_length +from plenum.server.plugin.did_plugin.constants import GET_BAL + + +def test_plugin_setup(txn_pool_node_set_post_creation): + """ + Test that plugin's ledger and state are setup + """ + for node in txn_pool_node_set_post_creation: + assert AUCTION_LEDGER_ID in node.ledger_ids + assert AUCTION_LEDGER_ID in node.ledgerManager.ledgerRegistry + assert node.ledger_ids == node.ledgerManager.ledger_sync_order + assert AUCTION_LEDGER_ID in node.states + + +def test_plugin_client_req_fields(txn_pool_node_set_post_creation, looper, + sdk_wallet_steward, sdk_pool_handle): + """ + Test that plugin's addition of request fields and their validation is + successful + """ + op = { + TXN_TYPE: GET_BAL, + DATA: {'id': '123'} + } + + # Valid field value results in successful processing + req_obj = sdk_gen_request(op, identifier=sdk_wallet_steward[1], + fix_length_dummy=randomString(dummy_field_length)) + req = sdk_sign_and_submit_req_obj(looper, sdk_pool_handle, sdk_wallet_steward, + req_obj) + sdk_get_reply(looper, req) + + # Invalid field value results in proper failure + _, did = sdk_wallet_steward + req = sdk_gen_request(op, identifier=did, fix_length_dummy=randomString(dummy_field_length + 1)) + reqs = sdk_sign_request_objects(looper, sdk_wallet_steward, [req]) + reqs = sdk_send_signed_requests(sdk_pool_handle, reqs) + + with pytest.raises(RequestNackedException) as e: + sdk_get_and_check_replies(looper, reqs) + assert 'should have length' in e._excinfo[1].args[0] diff --git a/plenum/server/plugin/did_plugin/test_plugin_removing.py b/plenum/server/plugin/did_plugin/test_plugin_removing.py new file mode 100644 index 0000000000..8dfaadb4d8 --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_plugin_removing.py @@ -0,0 +1,126 @@ +import pytest + +from plenum.common.constants import DATA +from plenum.common.exceptions import RequestNackedException, RequestRejectedException +from plenum.test.freeze_ledgers.helper import sdk_send_freeze_ledgers + +from plenum.test.node_catchup.helper import ensure_all_nodes_have_same_data +from plenum.test.node_request.helper import sdk_ensure_pool_functional +from plenum.server.plugin.did_plugin.constants import GET_AUCTION, AUCTION_START +from plenum.test.test_node import ensureElectionsDone + +from plenum.test.pool_transactions.helper import sdk_build_get_txn_request, sdk_sign_and_send_prepared_request + +from plenum.common.txn_util import get_seq_no, get_payload_data +from plenum.test.freshness.helper import check_freshness_updated_for_ledger +from plenum.test.helper import freshness, sdk_get_and_check_replies, sdk_send_random_and_check +from plenum.server.plugin.did_plugin import AUCTION_LEDGER_ID +from plenum.server.plugin.did_plugin.helper import send_auction_txn, send_get_auction_txn, restart_nodes +from stp_core.loop.eventually import eventually + +FRESHNESS_TIMEOUT = 5 + + +@pytest.fixture(scope="module") +def tconf(tconf): + with freshness(tconf, enabled=True, timeout=FRESHNESS_TIMEOUT): + yield tconf + + +def check_get_auction_txn(expected_result, + looper, + sdk_wallet_steward, + sdk_pool_handle): + seqNo = get_seq_no(expected_result) + + _, steward_did = sdk_wallet_steward + request = sdk_build_get_txn_request(looper, steward_did, seqNo, ledger_type=str(AUCTION_LEDGER_ID)) + + request_couple = \ + sdk_sign_and_send_prepared_request(looper, + sdk_wallet_steward, + sdk_pool_handle, + request) + result = sdk_get_and_check_replies(looper, + [request_couple])[0][1]['result'] + + assert expected_result['reqSignature'] == result['data']['reqSignature'] + assert expected_result['txn'] == result['data']['txn'] + assert expected_result['txnMetadata'] == result['data']['txnMetadata'] + assert expected_result['rootHash'] == result['data']['rootHash'] + assert expected_result['ver'] == result['data']['ver'] + assert expected_result['auditPath'] == result['data']['auditPath'] + + +def test_plugin_removing(looper, tconf, txn_pool_node_set_post_creation, + sdk_pool_handle, sdk_wallet_steward, sdk_wallet_trustee, tdir, allPluginsPath): + """ + Send a transaction from the plugin + Wait for recording a freshness txn + Check that reading the plugin transaction is successful + Check that sending a write plugin transaction is successful + Freeze the auction ledger + Check that reading the plugin transaction is successful + Check that sending a write plugin transaction is failed + Restart the pool without plugins + Check that reading the plugin transaction is failed + Check that sending a write plugin transaction is failed + Send an ordinary transaction + Check pool for freshness and consensus + """ + txnPoolNodeSet = txn_pool_node_set_post_creation + + # Update auction ledger + result = send_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward)[0][1]["result"] + + # get txn + auction_id, auction_name = list(get_payload_data(result)[DATA].items())[0] + get_auction_result = send_get_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward) + assert get_auction_result[0][1]["result"][auction_id] == auction_name + + # Wait for the first freshness update + looper.run(eventually( + check_freshness_updated_for_ledger, txnPoolNodeSet, AUCTION_LEDGER_ID, + timeout=3 * FRESHNESS_TIMEOUT) + ) + + sdk_send_freeze_ledgers( + looper, sdk_pool_handle, + [sdk_wallet_trustee], + [AUCTION_LEDGER_ID] + ) + + with pytest.raises(RequestRejectedException, + match="'{}' transaction is forbidden because of " + "'{}' ledger is frozen".format(AUCTION_START, AUCTION_LEDGER_ID)): + send_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward) + + # should failed with "ledger is frozen" + get_auction_result = send_get_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward) + assert get_auction_result[0][1]["result"][auction_id] == auction_name + + # restart pool + restart_nodes(looper, txnPoolNodeSet, txnPoolNodeSet, tconf, tdir, allPluginsPath, start_one_by_one=True) + + # get txn should failed with "ledger not exists" + with pytest.raises(RequestNackedException, + match="unknown value '" + str(AUCTION_LEDGER_ID)): + check_get_auction_txn(result, looper, + sdk_wallet_steward, + sdk_pool_handle) + + # should failed with "unknown txn" + with pytest.raises(RequestNackedException, + match="invalid type: " + GET_AUCTION): + send_get_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward) + + with pytest.raises(RequestNackedException, + match="invalid type: " + AUCTION_START): + send_auction_txn(looper, sdk_pool_handle, sdk_wallet_steward) + + sdk_send_random_and_check(looper, txnPoolNodeSet, sdk_pool_handle, sdk_wallet_steward, 1) + + # make sure that all node have equal primaries and can order + ensureElectionsDone(looper, txnPoolNodeSet, customTimeout=30) + ensure_all_nodes_have_same_data(looper, txnPoolNodeSet, custom_timeout=20) + sdk_ensure_pool_functional(looper, txnPoolNodeSet, sdk_wallet_steward, sdk_pool_handle) diff --git a/plenum/server/plugin/did_plugin/test_plugin_request_handling.py b/plenum/server/plugin/did_plugin/test_plugin_request_handling.py new file mode 100644 index 0000000000..769007f3be --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_plugin_request_handling.py @@ -0,0 +1,149 @@ +import pytest + +from plenum.common.constants import TXN_TYPE, DATA +from plenum.common.exceptions import CommonSdkIOException +from plenum.server.plugin.did_plugin.helper import successful_op +from plenum.test.helper import sdk_send_signed_requests, \ + sdk_sign_request_strings, sdk_get_and_check_replies +from plenum.server.plugin.did_plugin.constants import AMOUNT, PLACE_BID, \ + AUCTION_START, AUCTION_END +from stp_core.loop.eventually import eventually + +whitelist = ["Can't parse parsed_req or op from message", ] + + +def test_plugin_static_validation(txn_pool_node_set_post_creation, looper, + sdk_wallet_steward, sdk_pool_handle): + """ + Check plugin static validation fails and passes + """ + op = { + TXN_TYPE: AUCTION_START + } + reqs = sdk_sign_request_strings(looper, sdk_wallet_steward, [op, ]) + reqs = sdk_send_signed_requests(sdk_pool_handle, reqs) + with pytest.raises(CommonSdkIOException) as exc_info: + sdk_get_and_check_replies(looper, reqs) + exc_info.match('Got an error with code 113') + + op = { + TXN_TYPE: AUCTION_START, + DATA: 'should be a dict but giving a string' + } + reqs = sdk_sign_request_strings(looper, sdk_wallet_steward, [op, ]) + reqs = sdk_send_signed_requests(sdk_pool_handle, reqs) + with pytest.raises(CommonSdkIOException) as exc_info: + sdk_get_and_check_replies(looper, reqs) + exc_info.match('Got an error with code 113') + + op = { + TXN_TYPE: AUCTION_START, + DATA: {'id': 'abc'} + } + + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + op = { + TXN_TYPE: PLACE_BID, + DATA: {'id': 'abc', AMOUNT: -3} + } + reqs = sdk_sign_request_strings(looper, sdk_wallet_steward, [op, ]) + reqs = sdk_send_signed_requests(sdk_pool_handle, reqs) + with pytest.raises(CommonSdkIOException) as exc_info: + sdk_get_and_check_replies(looper, reqs) + exc_info.match('Got an error with code 113') + + op = { + TXN_TYPE: PLACE_BID, + DATA: {'id': 'abc', AMOUNT: 20} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + +def test_plugin_dynamic_validation(txn_pool_node_set_post_creation, looper, + sdk_wallet_steward, sdk_pool_handle): + """ + Check plugin dynamic validation fails and passes + """ + op = { + TXN_TYPE: AUCTION_END, + DATA: {'id': 'abcdef'} + } + reqs = sdk_sign_request_strings(looper, sdk_wallet_steward, [op, ]) + reqs = sdk_send_signed_requests(sdk_pool_handle, reqs) + with pytest.raises(CommonSdkIOException) as exc_info: + sdk_get_and_check_replies(looper, reqs) + exc_info.match('Got an error with code 113') + + op = { + TXN_TYPE: AUCTION_START, + DATA: {'id': 'xyz'} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + op = { + TXN_TYPE: AUCTION_END, + DATA: {'id': 'xyz'} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + +@pytest.fixture(scope="module") +def some_requests(txn_pool_node_set_post_creation, looper, + sdk_wallet_steward, sdk_pool_handle): + def check_auctions_amount(auc, expected_amount): + assert did in auc['pqr'] + assert auc['pqr'][did] == expected_amount + + old_bls_store_size = None + for node in txn_pool_node_set_post_creation: + if old_bls_store_size is None: + old_bls_store_size = node.bls_bft.bls_store._kvs.size + else: + assert node.bls_bft.bls_store._kvs.size == old_bls_store_size + + op = { + TXN_TYPE: AUCTION_START, + DATA: {'id': 'pqr'} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + op = { + TXN_TYPE: PLACE_BID, + DATA: {'id': 'pqr', AMOUNT: 20} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + _, did = sdk_wallet_steward + for node in txn_pool_node_set_post_creation: + print(node.name) + auctions = node.write_manager.request_handlers[PLACE_BID][0].auctions + assert 'pqr' in auctions + looper.run(eventually(check_auctions_amount, auctions, 20)) + + op = { + TXN_TYPE: PLACE_BID, + DATA: {'id': 'pqr', AMOUNT: 40} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + + for node in txn_pool_node_set_post_creation: + auctions = node.write_manager.request_handlers[PLACE_BID][0].auctions + assert 'pqr' in auctions + looper.run(eventually(check_auctions_amount, auctions, 40)) + + op = { + TXN_TYPE: AUCTION_END, + DATA: {'id': 'pqr'} + } + successful_op(looper, op, sdk_wallet_steward, sdk_pool_handle) + for node in txn_pool_node_set_post_creation: + # Not all batches might have BLS-sig but at least one of them will have + assert node.bls_bft.bls_store._kvs.size > old_bls_store_size + + +def test_plugin_request_handling(some_requests): + """ + Check that plugin requests are applied and change plugin's internal state + """ + pass diff --git a/plenum/server/plugin/did_plugin/test_request_digest.py b/plenum/server/plugin/did_plugin/test_request_digest.py new file mode 100644 index 0000000000..a130dd18ad --- /dev/null +++ b/plenum/server/plugin/did_plugin/test_request_digest.py @@ -0,0 +1,78 @@ +import copy +import json + +import pytest + +from plenum import PLUGIN_CLIENT_REQUEST_FIELDS +from plenum.common.constants import CURRENT_PROTOCOL_VERSION, TXN_TYPE, DATA +from plenum.common.exceptions import RequestNackedException +from plenum.common.request import Request +from plenum.common.util import randomString + +from plenum.test.helper import sdk_random_request_objects, sdk_multisign_request_object, sdk_get_and_check_replies, \ + sdk_send_signed_requests, sdk_gen_request, sdk_sign_and_submit_req_obj, sdk_get_reply +from plenum.server.plugin.did_plugin import dummy_field_length +from plenum.server.plugin.did_plugin.constants import PLACE_BID, AMOUNT, AUCTION_START + + +@pytest.fixture(scope='function') +def two_requests(looper, sdk_wallet_steward): + wh, did = sdk_wallet_steward + + op = { + TXN_TYPE: AUCTION_START, + DATA: {'id': 'xyz'} + } + + req1 = sdk_gen_request(op, protocol_version=CURRENT_PROTOCOL_VERSION, + identifier=did).as_dict + field = list(PLUGIN_CLIENT_REQUEST_FIELDS.keys())[0] + req1[field] = 'x' * 10 + + req2 = copy.deepcopy(req1) + req2[field] = 'z' * 10 + + req1 = sdk_multisign_request_object(looper, sdk_wallet_steward, json.dumps(req1)) + req_obj1 = Request(**json.loads(req1)) + + req2 = sdk_multisign_request_object(looper, sdk_wallet_steward, json.dumps(req2)) + req_obj2 = Request(**json.loads(req2)) + + assert req_obj1.payload_digest == req_obj2.payload_digest + assert req_obj1.digest != req_obj2.digest + return req1, req2 + + +def test_plugin_digest_match_to_written(txn_pool_node_set_post_creation, looper, + sdk_wallet_steward, sdk_pool_handle): + op = { + TXN_TYPE: AUCTION_START, + DATA: {'id': 'xyz'} + } + + # Valid field value results in successful processing + req_obj = sdk_gen_request(op, identifier=sdk_wallet_steward[1], + fix_length_dummy=randomString(dummy_field_length)) + req = sdk_sign_and_submit_req_obj(looper, sdk_pool_handle, sdk_wallet_steward, + req_obj) + sdk_get_and_check_replies(looper, [req]) + + req = Request(**req[0]) + value = txn_pool_node_set_post_creation[0].seqNoDB.get_by_full_digest(req.digest) + assert value == req.payload_digest + + ledger_id, seq_no = txn_pool_node_set_post_creation[0].seqNoDB.get_by_payload_digest(req.payload_digest) + assert ledger_id is not None and seq_no is not None + + +def test_send_same_txn_with_different_plugins( + looper, txn_pool_node_set_post_creation, sdk_pool_handle, two_requests): + req1, req2 = two_requests + + rep1 = sdk_send_signed_requests(sdk_pool_handle, [req1]) + sdk_get_and_check_replies(looper, rep1) + + rep2 = sdk_send_signed_requests(sdk_pool_handle, [req2]) + with pytest.raises(RequestNackedException) as e: + sdk_get_and_check_replies(looper, rep2) + e.match('Same txn was already ordered with different signatures or pluggable fields') diff --git a/plenum/server/plugin/did_plugin/transactions.py b/plenum/server/plugin/did_plugin/transactions.py new file mode 100644 index 0000000000..b08bc3992d --- /dev/null +++ b/plenum/server/plugin/did_plugin/transactions.py @@ -0,0 +1,21 @@ +from plenum.common.transactions import Transactions + + +# DO NOT CHANGE ONCE CODE IS DEPLOYED ON THE LEDGER +# TODO: Might need a short hash with unique entropy, plugin name being input of hash. +PREFIX = '7777' + + +class DemoTransactions(Transactions): + # These numeric constants CANNOT be changed once they have been used, + # because that would break backwards compatibility with the ledger + # Also the numeric constants CANNOT collide with other transactions hence a + # prefix is used + CREATE_DID = PREFIX + '0' + CREATE_NETWORK_DID = PREFIX + '1' + FETCH_DID = PREFIX + '2' + UPDATE_DID = PREFIX + '3' + UPDATE_NETWORK_DID = PREFIX + '4' + DEACTIVATE_DID = PREFIX + '5' + OUDID = PREFIX + '6' + SDDID = PREFIX + '7' \ No newline at end of file