diff --git a/da/api/__init__.py b/da/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/da/api/common.py b/da/api/common.py new file mode 100644 index 0000000..0d8afc9 --- /dev/null +++ b/da/api/common.py @@ -0,0 +1,50 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Optional, List, Sequence + +from da.common import Certificate +from da.verifier import DABlob + + +@dataclass +class Metadata: + # index of VID certificate blob + index: int + # app identifier + app_id: bytes + + +class BlobStore(ABC): + @abstractmethod + def add(self, certificate: Certificate, metadata: Metadata): + """ + Raises: ValueError if there is already a registered certificate fot the given metadata + """ + pass + + @abstractmethod + def get_multiple(self, app_id: bytes, indexes: Sequence[int]) -> List[Optional[DABlob]]: + pass + + +class DAApi: + def __init__(self, bs: BlobStore): + self.store = bs + + def write(self, certificate: Certificate, metadata: Metadata): + """ + Write method should be used by a service that is able to retrieve verified certificates + from the latest Block. Once a certificate is retrieved, api creates a relation between + the blob of an original data, certificate and index for the app_id of the certificate. + Raises: ValueError if there is already a registered certificate for a given metadata + """ + self.store.add(certificate, metadata) + + def read(self, app_id, indexes) -> List[Optional[DABlob]]: + """ + Read method should accept only `app_id` and a list of indexes. The returned list of + blobs should be ordered in the same sequence as `indexes` in a request. + If node does not have the blob for some indexes, then it should add None object as an + item. + """ + return self.store.get_multiple(app_id, indexes) diff --git a/da/api/test_flow.py b/da/api/test_flow.py new file mode 100644 index 0000000..bf88fc1 --- /dev/null +++ b/da/api/test_flow.py @@ -0,0 +1,72 @@ +from unittest import TestCase +from collections import defaultdict + +from da.api.common import * + + +@dataclass +class MockCertificate: + cert_id: int + + +class MockStore(BlobStore): + def __init__(self): + self.blob_store = {} + self.app_id_store = defaultdict(dict) + + def populate(self, blob, cert_id: bytes): + self.blob_store[cert_id] = blob + + # Implements `add` method from BlobStore abstract class. + def add(self, cert_id: bytes, metadata: Metadata): + if metadata.index in self.app_id_store[metadata.app_id]: + raise ValueError("index already written") + + blob = self.blob_store.pop(cert_id) + self.app_id_store[metadata.app_id][metadata.index] = blob + + # Implements `get_multiple` method from BlobStore abstract class. + def get_multiple(self, app_id, indexes) -> List[Optional[DABlob]]: + return [ + self.app_id_store[app_id].get(i) for i in indexes + ] + + +class TestFlow(TestCase): + def test_api_write_read(self): + expected_blob = "hello" + cert_id = b"11"*32 + app_id = 1 + idx = 1 + mock_meta = Metadata(1, 1) + + mock_store = MockStore() + mock_store.populate(expected_blob, cert_id) + + api = DAApi(mock_store) + + api.write(cert_id, mock_meta) + blobs = api.read(app_id, [idx]) + + self.assertEqual([expected_blob], blobs) + + def test_same_index(self): + expected_blob = "hello" + cert_id = b"11"*32 + app_id = 1 + idx = 1 + mock_meta = Metadata(1, 1) + + mock_store = MockStore() + mock_store.populate(expected_blob, cert_id) + + api = DAApi(mock_store) + + api.write(cert_id, mock_meta) + with self.assertRaises(ValueError): + api.write(cert_id, mock_meta) + + blobs = api.read(app_id, [idx]) + + self.assertEqual([expected_blob], blobs) +