dkg 0.1.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dkg/__init__.py +3 -0
- dkg/asset.py +781 -0
- dkg/constants.py +39 -0
- dkg/data/interfaces/Assertion.json +131 -0
- dkg/data/interfaces/AssertionStorage.json +229 -0
- dkg/data/interfaces/CommitManagerV1.json +534 -0
- dkg/data/interfaces/CommitManagerV1U1.json +720 -0
- dkg/data/interfaces/ContentAsset.json +671 -0
- dkg/data/interfaces/ContentAssetStorage.json +706 -0
- dkg/data/interfaces/HashingProxy.json +227 -0
- dkg/data/interfaces/Hub.json +356 -0
- dkg/data/interfaces/Identity.json +193 -0
- dkg/data/interfaces/IdentityStorage.json +342 -0
- dkg/data/interfaces/ParametersStorage.json +468 -0
- dkg/data/interfaces/Profile.json +292 -0
- dkg/data/interfaces/ProfileStorage.json +596 -0
- dkg/data/interfaces/ProofManagerV1.json +525 -0
- dkg/data/interfaces/ProofManagerV1U1.json +546 -0
- dkg/data/interfaces/ScoringProxy.json +242 -0
- dkg/data/interfaces/ServiceAgreementStorageProxy.json +1299 -0
- dkg/data/interfaces/ServiceAgreementStorageV1.json +901 -0
- dkg/data/interfaces/ServiceAgreementStorageV1U1.json +1097 -0
- dkg/data/interfaces/ServiceAgreementV1.json +741 -0
- dkg/data/interfaces/ShardingTable.json +268 -0
- dkg/data/interfaces/ShardingTableStorage.json +317 -0
- dkg/data/interfaces/Staking.json +456 -0
- dkg/data/interfaces/StakingStorage.json +407 -0
- dkg/data/interfaces/Token.json +544 -0
- dkg/data/interfaces/UnfinalizedStateStorage.json +171 -0
- dkg/data/interfaces/WhitelistStorage.json +124 -0
- dkg/dataclasses.py +45 -0
- dkg/exceptions.py +161 -0
- dkg/graph.py +63 -0
- dkg/main.py +74 -0
- dkg/manager.py +64 -0
- dkg/method.py +131 -0
- dkg/module.py +63 -0
- dkg/node.py +54 -0
- dkg/providers/__init__.py +2 -0
- dkg/providers/blockchain.py +181 -0
- dkg/providers/node_http.py +62 -0
- dkg/types/__init__.py +8 -0
- dkg/types/blockchain.py +58 -0
- dkg/types/dkg_node.py +20 -0
- dkg/types/encoding.py +22 -0
- dkg/types/evm.py +25 -0
- dkg/types/generics.py +21 -0
- dkg/types/network.py +20 -0
- dkg/types/rdf.py +21 -0
- dkg/utils/__init__.py +0 -0
- dkg/utils/blockchain_request.py +159 -0
- dkg/utils/decorators.py +46 -0
- dkg/utils/merkle.py +173 -0
- dkg/utils/metadata.py +50 -0
- dkg/utils/node_request.py +197 -0
- dkg/utils/rdf.py +51 -0
- dkg/utils/string_transformations.py +22 -0
- dkg/utils/ual.py +41 -0
- dkg-0.1.0b1.dist-info/LICENSE +202 -0
- dkg-0.1.0b1.dist-info/METADATA +453 -0
- dkg-0.1.0b1.dist-info/RECORD +62 -0
- dkg-0.1.0b1.dist-info/WHEEL +4 -0
dkg/asset.py
ADDED
@@ -0,0 +1,781 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
import math
|
19
|
+
import re
|
20
|
+
from typing import Literal, Type
|
21
|
+
|
22
|
+
from pyld import jsonld
|
23
|
+
from web3 import Web3
|
24
|
+
from web3.constants import HASH_ZERO
|
25
|
+
from web3.exceptions import ContractLogicError
|
26
|
+
|
27
|
+
from dkg.constants import BLOCKCHAINS, PRIVATE_ASSERTION_PREDICATE
|
28
|
+
from dkg.dataclasses import (KnowledgeAssetContentVisibility,
|
29
|
+
KnowledgeAssetEnumStates, NodeResponseDict)
|
30
|
+
from dkg.exceptions import (DatasetOutputFormatNotSupported,
|
31
|
+
InvalidKnowledgeAsset, InvalidStateOption,
|
32
|
+
InvalidTokenAmount, MissingKnowledgeAssetState,
|
33
|
+
OperationNotFinished)
|
34
|
+
from dkg.manager import DefaultRequestManager
|
35
|
+
from dkg.method import Method
|
36
|
+
from dkg.module import Module
|
37
|
+
from dkg.types import JSONLD, UAL, Address, AgreementData, HexStr, NQuads
|
38
|
+
from dkg.utils.blockchain_request import BlockchainRequest
|
39
|
+
from dkg.utils.decorators import retry
|
40
|
+
from dkg.utils.merkle import MerkleTree, hash_assertion_with_indexes
|
41
|
+
from dkg.utils.metadata import (generate_agreement_id,
|
42
|
+
generate_assertion_metadata, generate_keyword)
|
43
|
+
from dkg.utils.node_request import (NodeRequest, StoreTypes,
|
44
|
+
validate_operation_status)
|
45
|
+
from dkg.utils.rdf import normalize_dataset
|
46
|
+
from dkg.utils.ual import format_ual, parse_ual
|
47
|
+
|
48
|
+
|
49
|
+
class ContentAsset(Module):
|
50
|
+
DEFAULT_HASH_FUNCTION_ID = 1
|
51
|
+
DEFAULT_SCORE_FUNCTION_ID = 1
|
52
|
+
PRIVATE_HISTORICAL_REPOSITORY = "privateHistory"
|
53
|
+
PRIVATE_CURRENT_REPOSITORY = "privateCurrent"
|
54
|
+
|
55
|
+
def __init__(self, manager: DefaultRequestManager):
|
56
|
+
self.manager = manager
|
57
|
+
|
58
|
+
_chain_id = Method(BlockchainRequest.chain_id)
|
59
|
+
|
60
|
+
_get_contract_address = Method(BlockchainRequest.get_contract_address)
|
61
|
+
_get_asset_storage_address = Method(BlockchainRequest.get_asset_storage_address)
|
62
|
+
_increase_allowance = Method(BlockchainRequest.increase_allowance)
|
63
|
+
_decrease_allowance = Method(BlockchainRequest.decrease_allowance)
|
64
|
+
_create = Method(BlockchainRequest.create_asset)
|
65
|
+
|
66
|
+
_get_bid_suggestion = Method(NodeRequest.bid_suggestion)
|
67
|
+
_local_store = Method(NodeRequest.local_store)
|
68
|
+
_publish = Method(NodeRequest.publish)
|
69
|
+
|
70
|
+
def create(
|
71
|
+
self,
|
72
|
+
content: dict[Literal["public", "private"], JSONLD],
|
73
|
+
epochs_number: int,
|
74
|
+
token_amount: int | None = None,
|
75
|
+
immutable: bool = False,
|
76
|
+
content_type: Literal["JSON-LD", "N-Quads"] = "JSON-LD",
|
77
|
+
) -> dict[str, HexStr | dict[str, str]]:
|
78
|
+
assertions = self._process_content(content, content_type)
|
79
|
+
|
80
|
+
chain_name = BLOCKCHAINS[self._chain_id()]["name"]
|
81
|
+
content_asset_storage_address = self._get_asset_storage_address(
|
82
|
+
"ContentAssetStorage"
|
83
|
+
)
|
84
|
+
|
85
|
+
if token_amount is None:
|
86
|
+
token_amount = int(
|
87
|
+
self._get_bid_suggestion(
|
88
|
+
chain_name,
|
89
|
+
epochs_number,
|
90
|
+
assertions["public"]["size"],
|
91
|
+
content_asset_storage_address,
|
92
|
+
assertions["public"]["id"],
|
93
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
94
|
+
)["bidSuggestion"]
|
95
|
+
)
|
96
|
+
|
97
|
+
service_agreement_v1_address = str(
|
98
|
+
self._get_contract_address("ServiceAgreementV1")
|
99
|
+
)
|
100
|
+
self._increase_allowance(service_agreement_v1_address, token_amount)
|
101
|
+
|
102
|
+
try:
|
103
|
+
receipt = self._create(
|
104
|
+
{
|
105
|
+
"assertionId": Web3.to_bytes(hexstr=assertions["public"]["id"]),
|
106
|
+
"size": assertions["public"]["size"],
|
107
|
+
"triplesNumber": assertions["public"]["triples_number"],
|
108
|
+
"chunksNumber": assertions["public"]["chunks_number"],
|
109
|
+
"tokenAmount": token_amount,
|
110
|
+
"epochsNumber": epochs_number,
|
111
|
+
"scoreFunctionId": self.DEFAULT_SCORE_FUNCTION_ID,
|
112
|
+
"immutable_": immutable,
|
113
|
+
}
|
114
|
+
)
|
115
|
+
except ContractLogicError as err:
|
116
|
+
self._decrease_allowance(service_agreement_v1_address, token_amount)
|
117
|
+
raise err
|
118
|
+
|
119
|
+
events = self.manager.blockchain_provider.decode_logs_event(
|
120
|
+
receipt,
|
121
|
+
"ContentAsset",
|
122
|
+
"AssetMinted",
|
123
|
+
)
|
124
|
+
token_id = events[0].args["tokenId"]
|
125
|
+
|
126
|
+
assertions_list = [
|
127
|
+
{
|
128
|
+
"blockchain": chain_name,
|
129
|
+
"contract": content_asset_storage_address,
|
130
|
+
"tokenId": token_id,
|
131
|
+
"assertionId": assertions["public"]["id"],
|
132
|
+
"assertion": assertions["public"]["content"],
|
133
|
+
"storeType": StoreTypes.TRIPLE.value,
|
134
|
+
}
|
135
|
+
]
|
136
|
+
|
137
|
+
if content.get("private", None):
|
138
|
+
assertions_list.append(
|
139
|
+
{
|
140
|
+
"blockchain": chain_name,
|
141
|
+
"contract": content_asset_storage_address,
|
142
|
+
"tokenId": token_id,
|
143
|
+
"assertionId": assertions["private"]["id"],
|
144
|
+
"assertion": assertions["private"]["content"],
|
145
|
+
"storeType": StoreTypes.TRIPLE.value,
|
146
|
+
}
|
147
|
+
)
|
148
|
+
|
149
|
+
operation_id = self._local_store(assertions_list)["operationId"]
|
150
|
+
self.get_operation_result(operation_id, "local-store")
|
151
|
+
|
152
|
+
operation_id = self._publish(
|
153
|
+
assertions["public"]["id"],
|
154
|
+
assertions["public"]["content"],
|
155
|
+
chain_name,
|
156
|
+
content_asset_storage_address,
|
157
|
+
token_id,
|
158
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
159
|
+
)["operationId"]
|
160
|
+
operation_result = self.get_operation_result(operation_id, "publish")
|
161
|
+
|
162
|
+
return {
|
163
|
+
"UAL": format_ual(chain_name, content_asset_storage_address, token_id),
|
164
|
+
"publicAssertionId": assertions["public"]["id"],
|
165
|
+
"operation": {
|
166
|
+
"operationId": operation_id,
|
167
|
+
"status": operation_result["status"],
|
168
|
+
},
|
169
|
+
}
|
170
|
+
|
171
|
+
_transfer = Method(BlockchainRequest.transfer_asset)
|
172
|
+
|
173
|
+
def transfer(
|
174
|
+
self,
|
175
|
+
ual: UAL,
|
176
|
+
new_owner: Address,
|
177
|
+
) -> dict[str, UAL | Address | dict[str, str]]:
|
178
|
+
token_id = parse_ual(ual)["token_id"]
|
179
|
+
|
180
|
+
self._transfer(
|
181
|
+
self.manager.blockchain_provider.account,
|
182
|
+
new_owner,
|
183
|
+
token_id,
|
184
|
+
)
|
185
|
+
|
186
|
+
return {
|
187
|
+
"UAL": ual,
|
188
|
+
"owner": new_owner,
|
189
|
+
"operation": {"status": "COMPLETED"},
|
190
|
+
}
|
191
|
+
|
192
|
+
_update = Method(NodeRequest.update)
|
193
|
+
|
194
|
+
_get_block = Method(BlockchainRequest.get_block)
|
195
|
+
|
196
|
+
_get_service_agreement_data = Method(BlockchainRequest.get_service_agreement_data)
|
197
|
+
_update_asset_state = Method(BlockchainRequest.update_asset_state)
|
198
|
+
|
199
|
+
def update(
|
200
|
+
self,
|
201
|
+
ual: UAL,
|
202
|
+
content: dict[Literal["public", "private"], JSONLD],
|
203
|
+
token_amount: int | None = None,
|
204
|
+
content_type: Literal["JSON-LD", "N-Quads"] = "JSON-LD",
|
205
|
+
) -> dict[str, HexStr | dict[str, str]]:
|
206
|
+
parsed_ual = parse_ual(ual)
|
207
|
+
content_asset_storage_address, token_id = (
|
208
|
+
parsed_ual["contract_address"],
|
209
|
+
parsed_ual["token_id"],
|
210
|
+
)
|
211
|
+
|
212
|
+
assertions = self._process_content(content, content_type)
|
213
|
+
|
214
|
+
chain_name = BLOCKCHAINS[self._chain_id()]["name"]
|
215
|
+
|
216
|
+
if token_amount is None:
|
217
|
+
agreement_id = self.get_agreement_id(
|
218
|
+
content_asset_storage_address, token_id
|
219
|
+
)
|
220
|
+
# TODO: Dynamic types for namedtuples?
|
221
|
+
agreement_data: Type[AgreementData] = self._get_service_agreement_data(
|
222
|
+
agreement_id
|
223
|
+
)
|
224
|
+
|
225
|
+
timestamp_now = self._get_block("latest")["timestamp"]
|
226
|
+
current_epoch = math.floor(
|
227
|
+
(timestamp_now - agreement_data.startTime) / agreement_data.epochLength
|
228
|
+
)
|
229
|
+
epochs_left = agreement_data.epochsNumber - current_epoch
|
230
|
+
|
231
|
+
token_amount = int(
|
232
|
+
self._get_bid_suggestion(
|
233
|
+
chain_name,
|
234
|
+
epochs_left,
|
235
|
+
assertions["public"]["size"],
|
236
|
+
content_asset_storage_address,
|
237
|
+
assertions["public"]["id"],
|
238
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
239
|
+
)["bidSuggestion"]
|
240
|
+
)
|
241
|
+
|
242
|
+
token_amount -= agreement_data.tokensInfo[0]
|
243
|
+
token_amount = token_amount if token_amount > 0 else 0
|
244
|
+
|
245
|
+
self._update_asset_state(
|
246
|
+
token_id=token_id,
|
247
|
+
assertion_id=assertions["public"]["id"],
|
248
|
+
size=assertions["public"]["size"],
|
249
|
+
triples_number=assertions["public"]["triples_number"],
|
250
|
+
chunks_number=assertions["public"]["chunks_number"],
|
251
|
+
update_token_amount=token_amount,
|
252
|
+
)
|
253
|
+
|
254
|
+
assertions_list = [
|
255
|
+
{
|
256
|
+
"blockchain": chain_name,
|
257
|
+
"contract": content_asset_storage_address,
|
258
|
+
"tokenId": token_id,
|
259
|
+
"assertionId": assertions["public"]["id"],
|
260
|
+
"assertion": assertions["public"]["content"],
|
261
|
+
"storeType": StoreTypes.PENDING.value,
|
262
|
+
}
|
263
|
+
]
|
264
|
+
|
265
|
+
if content.get("private", None):
|
266
|
+
assertions_list.append(
|
267
|
+
{
|
268
|
+
"blockchain": chain_name,
|
269
|
+
"contract": content_asset_storage_address,
|
270
|
+
"tokenId": token_id,
|
271
|
+
"assertionId": assertions["private"]["id"],
|
272
|
+
"assertion": assertions["private"]["content"],
|
273
|
+
"storeType": StoreTypes.PENDING.value,
|
274
|
+
}
|
275
|
+
)
|
276
|
+
|
277
|
+
operation_id = self._local_store(assertions_list)["operationId"]
|
278
|
+
self.get_operation_result(operation_id, "local-store")
|
279
|
+
|
280
|
+
operation_id = self._update(
|
281
|
+
assertions["public"]["id"],
|
282
|
+
assertions["public"]["content"],
|
283
|
+
chain_name,
|
284
|
+
content_asset_storage_address,
|
285
|
+
token_id,
|
286
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
287
|
+
)["operationId"]
|
288
|
+
operation_result = self.get_operation_result(operation_id, "update")
|
289
|
+
|
290
|
+
return {
|
291
|
+
"UAL": format_ual(chain_name, content_asset_storage_address, token_id),
|
292
|
+
"publicAssertionId": assertions["public"]["id"],
|
293
|
+
"operation": {
|
294
|
+
"operationId": operation_id,
|
295
|
+
"status": operation_result["status"],
|
296
|
+
},
|
297
|
+
}
|
298
|
+
|
299
|
+
_cancel_update = Method(BlockchainRequest.cancel_asset_state_update)
|
300
|
+
|
301
|
+
def cancel_update(self, ual: UAL) -> dict[str, UAL | dict[str, str]]:
|
302
|
+
token_id = parse_ual(ual)["token_id"]
|
303
|
+
|
304
|
+
self._cancel_update(token_id)
|
305
|
+
|
306
|
+
return {
|
307
|
+
"UAL": ual,
|
308
|
+
"operation": {"status": "COMPLETED"},
|
309
|
+
}
|
310
|
+
|
311
|
+
_burn_asset = Method(BlockchainRequest.burn_asset)
|
312
|
+
|
313
|
+
def burn(self, ual: UAL) -> dict[str, UAL | dict[str, str]]:
|
314
|
+
token_id = parse_ual(ual)["token_id"]
|
315
|
+
|
316
|
+
self._burn_asset(token_id)
|
317
|
+
|
318
|
+
return {"UAL": ual, "operation": {"status": "COMPLETED"}}
|
319
|
+
|
320
|
+
_get_assertion_ids = Method(BlockchainRequest.get_assertion_ids)
|
321
|
+
_get_latest_assertion_id = Method(BlockchainRequest.get_latest_assertion_id)
|
322
|
+
_get_unfinalized_state = Method(BlockchainRequest.get_unfinalized_state)
|
323
|
+
|
324
|
+
_get = Method(NodeRequest.get)
|
325
|
+
_query = Method(NodeRequest.query)
|
326
|
+
|
327
|
+
def get(
|
328
|
+
self,
|
329
|
+
ual: UAL,
|
330
|
+
state: str | HexStr | int = KnowledgeAssetEnumStates.LATEST.value,
|
331
|
+
content_visibility: str = KnowledgeAssetContentVisibility.ALL.value,
|
332
|
+
output_format: Literal["JSON-LD", "N-Quads"] = "JSON-LD",
|
333
|
+
validate: bool = True,
|
334
|
+
) -> dict[str, HexStr | list[JSONLD] | dict[str, str]]:
|
335
|
+
state = (
|
336
|
+
state.upper()
|
337
|
+
if (isinstance(state, str) and not re.match(r"^0x[a-fA-F0-9]{64}$", state))
|
338
|
+
else state
|
339
|
+
)
|
340
|
+
content_visibility = content_visibility.upper()
|
341
|
+
output_format = output_format.upper()
|
342
|
+
|
343
|
+
token_id = parse_ual(ual)["token_id"]
|
344
|
+
|
345
|
+
def handle_latest_state(token_id: int) -> tuple[HexStr, bool]:
|
346
|
+
unfinalized_state = Web3.to_hex(self._get_unfinalized_state(token_id))
|
347
|
+
|
348
|
+
if unfinalized_state and unfinalized_state != HASH_ZERO:
|
349
|
+
return unfinalized_state, False
|
350
|
+
else:
|
351
|
+
return handle_latest_finalized_state(token_id)
|
352
|
+
|
353
|
+
def handle_latest_finalized_state(token_id: int) -> tuple[HexStr, bool]:
|
354
|
+
return Web3.to_hex(self._get_latest_assertion_id(token_id)), True
|
355
|
+
|
356
|
+
is_state_finalized = False
|
357
|
+
|
358
|
+
match state:
|
359
|
+
case KnowledgeAssetEnumStates.LATEST.value:
|
360
|
+
public_assertion_id, is_state_finalized = handle_latest_state(token_id)
|
361
|
+
|
362
|
+
case KnowledgeAssetEnumStates.LATEST_FINALIZED.value:
|
363
|
+
public_assertion_id, is_state_finalized = handle_latest_finalized_state(
|
364
|
+
token_id
|
365
|
+
)
|
366
|
+
|
367
|
+
case _ if isinstance(state, int):
|
368
|
+
assertion_ids = [
|
369
|
+
Web3.to_hex(assertion_id)
|
370
|
+
for assertion_id in self._get_assertion_ids(token_id)
|
371
|
+
]
|
372
|
+
if 0 <= state < (states_number := len(assertion_ids)):
|
373
|
+
public_assertion_id = assertion_ids[state]
|
374
|
+
|
375
|
+
if state == states_number - 1:
|
376
|
+
is_state_finalized = True
|
377
|
+
else:
|
378
|
+
raise InvalidStateOption(f"State index {state} is out of range.")
|
379
|
+
|
380
|
+
case _ if isinstance(state, str) and re.match(
|
381
|
+
r"^0x[a-fA-F0-9]{64}$", state
|
382
|
+
):
|
383
|
+
assertion_ids = [
|
384
|
+
Web3.to_hex(assertion_id)
|
385
|
+
for assertion_id in self._get_assertion_ids(token_id)
|
386
|
+
]
|
387
|
+
|
388
|
+
if state in assertion_ids:
|
389
|
+
public_assertion_id = state
|
390
|
+
|
391
|
+
if state == assertion_ids[-1]:
|
392
|
+
is_state_finalized = True
|
393
|
+
else:
|
394
|
+
raise InvalidStateOption(
|
395
|
+
f"Given state hash: {state} is not a part of the KA."
|
396
|
+
)
|
397
|
+
|
398
|
+
case _:
|
399
|
+
raise InvalidStateOption(f"Invalid state option: {state}.")
|
400
|
+
|
401
|
+
get_public_operation_id: NodeResponseDict = self._get(
|
402
|
+
ual, public_assertion_id, hashFunctionId=1
|
403
|
+
)["operationId"]
|
404
|
+
|
405
|
+
get_public_operation_result = self.get_operation_result(
|
406
|
+
get_public_operation_id, "get"
|
407
|
+
)
|
408
|
+
public_assertion = get_public_operation_result["data"].get("assertion", None)
|
409
|
+
|
410
|
+
if public_assertion is None:
|
411
|
+
raise MissingKnowledgeAssetState("Unable to find state on the network!")
|
412
|
+
|
413
|
+
if validate:
|
414
|
+
root = MerkleTree(
|
415
|
+
hash_assertion_with_indexes(public_assertion), sort_pairs=True
|
416
|
+
).root
|
417
|
+
if root != public_assertion_id:
|
418
|
+
raise InvalidKnowledgeAsset(
|
419
|
+
f"State: {public_assertion_id}. " f"Merkle Tree Root: {root}"
|
420
|
+
)
|
421
|
+
|
422
|
+
result = {"operation": {}}
|
423
|
+
if content_visibility != KnowledgeAssetContentVisibility.PRIVATE.value:
|
424
|
+
formatted_public_assertion = public_assertion
|
425
|
+
|
426
|
+
match output_format:
|
427
|
+
case "NQUADS" | "N-QUADS":
|
428
|
+
formatted_public_assertion: list[JSONLD] = jsonld.from_rdf(
|
429
|
+
"\n".join(public_assertion),
|
430
|
+
{"algorithm": "URDNA2015", "format": "application/n-quads"},
|
431
|
+
)
|
432
|
+
case "JSONLD" | "JSON-LD":
|
433
|
+
formatted_public_assertion = "\n".join(public_assertion)
|
434
|
+
|
435
|
+
case _:
|
436
|
+
raise DatasetOutputFormatNotSupported(
|
437
|
+
f"{output_format} isn't supported!"
|
438
|
+
)
|
439
|
+
|
440
|
+
if content_visibility == KnowledgeAssetContentVisibility.PUBLIC.value:
|
441
|
+
result = {
|
442
|
+
**result,
|
443
|
+
"asertion": formatted_public_assertion,
|
444
|
+
"assertionId": public_assertion_id,
|
445
|
+
}
|
446
|
+
else:
|
447
|
+
result["public"] = {
|
448
|
+
"assertion": formatted_public_assertion,
|
449
|
+
"assertionId": public_assertion_id,
|
450
|
+
}
|
451
|
+
|
452
|
+
result["operation"]["publicGet"] = {
|
453
|
+
"operationId": get_public_operation_id,
|
454
|
+
"status": get_public_operation_result["status"],
|
455
|
+
}
|
456
|
+
|
457
|
+
if content_visibility != KnowledgeAssetContentVisibility.PUBLIC.value:
|
458
|
+
private_assertion_link_triples = list(
|
459
|
+
filter(
|
460
|
+
lambda element: PRIVATE_ASSERTION_PREDICATE in element,
|
461
|
+
public_assertion,
|
462
|
+
)
|
463
|
+
)
|
464
|
+
|
465
|
+
if private_assertion_link_triples:
|
466
|
+
private_assertion_id = re.search(
|
467
|
+
r'"(.*?)"', private_assertion_link_triples[0]
|
468
|
+
).group(1)
|
469
|
+
|
470
|
+
private_assertion = get_public_operation_result["data"].get(
|
471
|
+
"privateAssertion", None
|
472
|
+
)
|
473
|
+
|
474
|
+
query_private_operation_id: NodeResponseDict | None = None
|
475
|
+
if private_assertion is None:
|
476
|
+
query = f"""
|
477
|
+
CONSTRUCT {{ ?s ?p ?o }}
|
478
|
+
WHERE {{
|
479
|
+
{{
|
480
|
+
GRAPH <assertion:{private_assertion_id}>
|
481
|
+
{{
|
482
|
+
?s ?p ?o .
|
483
|
+
}}
|
484
|
+
}}
|
485
|
+
}}
|
486
|
+
"""
|
487
|
+
|
488
|
+
query_private_operation_id = self._query(
|
489
|
+
query,
|
490
|
+
"CONSTRUCT",
|
491
|
+
self.PRIVATE_CURRENT_REPOSITORY
|
492
|
+
if is_state_finalized
|
493
|
+
else self.PRIVATE_HISTORICAL_REPOSITORY,
|
494
|
+
)["operationId"]
|
495
|
+
|
496
|
+
query_private_operation_result = self.get_operation_result(
|
497
|
+
query_private_operation_id, "query"
|
498
|
+
)
|
499
|
+
|
500
|
+
private_assertion = normalize_dataset(
|
501
|
+
query_private_operation_result["data"],
|
502
|
+
"N-Quads",
|
503
|
+
)
|
504
|
+
|
505
|
+
if validate:
|
506
|
+
root = MerkleTree(
|
507
|
+
hash_assertion_with_indexes(private_assertion),
|
508
|
+
sort_pairs=True,
|
509
|
+
).root
|
510
|
+
if root != private_assertion_id:
|
511
|
+
raise InvalidKnowledgeAsset(
|
512
|
+
f"State: {private_assertion_id}. "
|
513
|
+
f"Merkle Tree Root: {root}"
|
514
|
+
)
|
515
|
+
|
516
|
+
match output_format:
|
517
|
+
case "NQUADS" | "N-QUADS":
|
518
|
+
formatted_private_assertion: list[JSONLD] = jsonld.from_rdf(
|
519
|
+
"\n".join(private_assertion),
|
520
|
+
{
|
521
|
+
"algorithm": "URDNA2015",
|
522
|
+
"format": "application/n-quads",
|
523
|
+
},
|
524
|
+
)
|
525
|
+
case "JSONLD" | "JSON-LD":
|
526
|
+
formatted_private_assertion = "\n".join(private_assertion)
|
527
|
+
|
528
|
+
case _:
|
529
|
+
raise DatasetOutputFormatNotSupported(
|
530
|
+
f"{output_format} isn't supported!"
|
531
|
+
)
|
532
|
+
|
533
|
+
if content_visibility == KnowledgeAssetContentVisibility.PRIVATE:
|
534
|
+
result = {
|
535
|
+
**result,
|
536
|
+
"assertion": formatted_private_assertion,
|
537
|
+
"assertionId": private_assertion_id,
|
538
|
+
}
|
539
|
+
else:
|
540
|
+
result["private"] = {
|
541
|
+
"assertion": formatted_private_assertion,
|
542
|
+
"assertionId": private_assertion_id,
|
543
|
+
}
|
544
|
+
|
545
|
+
if query_private_operation_id is not None:
|
546
|
+
result["operation"]["queryPrivate"] = {
|
547
|
+
"operationId": query_private_operation_id,
|
548
|
+
"status": query_private_operation_result["status"],
|
549
|
+
}
|
550
|
+
|
551
|
+
return result
|
552
|
+
|
553
|
+
_extend_storing_period = Method(BlockchainRequest.extend_asset_storing_period)
|
554
|
+
|
555
|
+
def extend_storing_period(
|
556
|
+
self,
|
557
|
+
ual: UAL,
|
558
|
+
additional_epochs: int,
|
559
|
+
token_amount: int | None = None,
|
560
|
+
) -> dict[str, UAL | dict[str, str]]:
|
561
|
+
parsed_ual = parse_ual(ual)
|
562
|
+
content_asset_storage_address, token_id = (
|
563
|
+
parsed_ual["contract_address"],
|
564
|
+
parsed_ual["token_id"],
|
565
|
+
)
|
566
|
+
|
567
|
+
if token_amount is None:
|
568
|
+
chain_name = BLOCKCHAINS[self._chain_id()]["name"]
|
569
|
+
|
570
|
+
latest_finalized_state = self._get_latest_assertion_id(token_id)
|
571
|
+
latest_finalized_state_size = self._get_assertion_size(
|
572
|
+
latest_finalized_state
|
573
|
+
)
|
574
|
+
|
575
|
+
token_amount = int(
|
576
|
+
self._get_bid_suggestion(
|
577
|
+
chain_name,
|
578
|
+
additional_epochs,
|
579
|
+
latest_finalized_state_size,
|
580
|
+
content_asset_storage_address,
|
581
|
+
latest_finalized_state,
|
582
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
583
|
+
)["bidSuggestion"]
|
584
|
+
)
|
585
|
+
|
586
|
+
self._extend_storing_period(token_id, additional_epochs, token_amount)
|
587
|
+
|
588
|
+
return {
|
589
|
+
"UAL": ual,
|
590
|
+
"operation": {"status": "COMPLETED"},
|
591
|
+
}
|
592
|
+
|
593
|
+
_get_assertion_size = Method(BlockchainRequest.get_assertion_size)
|
594
|
+
_add_tokens = Method(BlockchainRequest.increase_asset_token_amount)
|
595
|
+
|
596
|
+
def add_tokens(
|
597
|
+
self,
|
598
|
+
ual: UAL,
|
599
|
+
token_amount: int | None = None,
|
600
|
+
) -> dict[str, UAL | dict[str, str]]:
|
601
|
+
parsed_ual = parse_ual(ual)
|
602
|
+
content_asset_storage_address, token_id = (
|
603
|
+
parsed_ual["contract_address"],
|
604
|
+
parsed_ual["token_id"],
|
605
|
+
)
|
606
|
+
|
607
|
+
if token_amount is None:
|
608
|
+
chain_name = BLOCKCHAINS[self._chain_id()]["name"]
|
609
|
+
|
610
|
+
agreement_id = self.get_agreement_id(
|
611
|
+
content_asset_storage_address, token_id
|
612
|
+
)
|
613
|
+
# TODO: Dynamic types for namedtuples?
|
614
|
+
agreement_data: Type[AgreementData] = self._get_service_agreement_data(
|
615
|
+
agreement_id
|
616
|
+
)
|
617
|
+
|
618
|
+
timestamp_now = self._get_block("latest")["timestamp"]
|
619
|
+
current_epoch = math.floor(
|
620
|
+
(timestamp_now - agreement_data.startTime) / agreement_data.epochLength
|
621
|
+
)
|
622
|
+
epochs_left = agreement_data.epochsNumber - current_epoch
|
623
|
+
|
624
|
+
latest_finalized_state = self._get_latest_assertion_id(token_id)
|
625
|
+
latest_finalized_state_size = self._get_assertion_size(
|
626
|
+
latest_finalized_state
|
627
|
+
)
|
628
|
+
|
629
|
+
token_amount = int(
|
630
|
+
self._get_bid_suggestion(
|
631
|
+
chain_name,
|
632
|
+
epochs_left,
|
633
|
+
latest_finalized_state_size,
|
634
|
+
content_asset_storage_address,
|
635
|
+
latest_finalized_state,
|
636
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
637
|
+
)["bidSuggestion"]
|
638
|
+
) - sum(agreement_data.tokensInfo)
|
639
|
+
|
640
|
+
if token_amount <= 0:
|
641
|
+
raise InvalidTokenAmount(
|
642
|
+
"Token amount is bigger than default suggested amount, "
|
643
|
+
"please specify exact token_amount if you still want to add "
|
644
|
+
"more tokens!"
|
645
|
+
)
|
646
|
+
|
647
|
+
self._add_tokens(token_id, token_amount)
|
648
|
+
|
649
|
+
return {
|
650
|
+
"UAL": ual,
|
651
|
+
"operation": {"status": "COMPLETED"},
|
652
|
+
}
|
653
|
+
|
654
|
+
_add_update_tokens = Method(BlockchainRequest.increase_asset_update_token_amount)
|
655
|
+
|
656
|
+
def add_update_tokens(
|
657
|
+
self,
|
658
|
+
ual: UAL,
|
659
|
+
token_amount: int | None = None,
|
660
|
+
) -> dict[str, UAL | dict[str, str]]:
|
661
|
+
parsed_ual = parse_ual(ual)
|
662
|
+
content_asset_storage_address, token_id = (
|
663
|
+
parsed_ual["contract_address"],
|
664
|
+
parsed_ual["token_id"],
|
665
|
+
)
|
666
|
+
|
667
|
+
if token_amount is None:
|
668
|
+
chain_name = BLOCKCHAINS[self._chain_id()]["name"]
|
669
|
+
|
670
|
+
agreement_id = self.get_agreement_id(
|
671
|
+
content_asset_storage_address, token_id
|
672
|
+
)
|
673
|
+
# TODO: Dynamic types for namedtuples?
|
674
|
+
agreement_data: Type[AgreementData] = self._get_service_agreement_data(
|
675
|
+
agreement_id
|
676
|
+
)
|
677
|
+
|
678
|
+
timestamp_now = self._get_block("latest")["timestamp"]
|
679
|
+
current_epoch = math.floor(
|
680
|
+
(timestamp_now - agreement_data.startTime) / agreement_data.epochLength
|
681
|
+
)
|
682
|
+
epochs_left = agreement_data.epochsNumber - current_epoch
|
683
|
+
|
684
|
+
unfinalized_state = self._get_latest_assertion_id(token_id)
|
685
|
+
unfinalized_state_size = self._get_assertion_size(unfinalized_state)
|
686
|
+
|
687
|
+
token_amount = int(
|
688
|
+
self._get_bid_suggestion(
|
689
|
+
chain_name,
|
690
|
+
epochs_left,
|
691
|
+
unfinalized_state_size,
|
692
|
+
content_asset_storage_address,
|
693
|
+
unfinalized_state,
|
694
|
+
self.DEFAULT_HASH_FUNCTION_ID,
|
695
|
+
)["bidSuggestion"]
|
696
|
+
) - sum(agreement_data.tokensInfo)
|
697
|
+
|
698
|
+
if token_amount <= 0:
|
699
|
+
raise InvalidTokenAmount(
|
700
|
+
"Token amount is bigger than default suggested amount, "
|
701
|
+
"please specify exact token_amount if you still want to add "
|
702
|
+
"more update tokens!"
|
703
|
+
)
|
704
|
+
|
705
|
+
self._add_update_tokens(token_id, token_amount)
|
706
|
+
|
707
|
+
return {
|
708
|
+
"UAL": ual,
|
709
|
+
"operation": {"status": "COMPLETED"},
|
710
|
+
}
|
711
|
+
|
712
|
+
_owner = Method(BlockchainRequest.owner_of)
|
713
|
+
|
714
|
+
def get_owner(self, ual: UAL) -> Address:
|
715
|
+
token_id = parse_ual(ual)["token_id"]
|
716
|
+
|
717
|
+
return self._owner(token_id)
|
718
|
+
|
719
|
+
def _process_content(
|
720
|
+
self,
|
721
|
+
content: dict[Literal["public", "private"], JSONLD],
|
722
|
+
type: Literal["JSON-LD", "N-Quads"] = "JSON-LD",
|
723
|
+
) -> dict[str, dict[str, HexStr | NQuads | int]]:
|
724
|
+
public_graph = {"@graph": []}
|
725
|
+
|
726
|
+
if content.get("public", None):
|
727
|
+
public_graph["@graph"].append(content["public"])
|
728
|
+
|
729
|
+
if content.get("private", None):
|
730
|
+
private_assertion = normalize_dataset(content["private"], type)
|
731
|
+
private_assertion_id = MerkleTree(
|
732
|
+
hash_assertion_with_indexes(private_assertion),
|
733
|
+
sort_pairs=True,
|
734
|
+
).root
|
735
|
+
|
736
|
+
public_graph["@graph"].append(
|
737
|
+
{PRIVATE_ASSERTION_PREDICATE: private_assertion_id}
|
738
|
+
)
|
739
|
+
|
740
|
+
public_assertion = normalize_dataset(public_graph, type)
|
741
|
+
public_assertion_id = MerkleTree(
|
742
|
+
hash_assertion_with_indexes(public_assertion),
|
743
|
+
sort_pairs=True,
|
744
|
+
).root
|
745
|
+
public_assertion_metadata = generate_assertion_metadata(public_assertion)
|
746
|
+
|
747
|
+
return {
|
748
|
+
"public": {
|
749
|
+
"id": public_assertion_id,
|
750
|
+
"content": public_assertion,
|
751
|
+
**public_assertion_metadata,
|
752
|
+
},
|
753
|
+
"private": {
|
754
|
+
"id": private_assertion_id,
|
755
|
+
"content": private_assertion,
|
756
|
+
}
|
757
|
+
if content.get("private", None)
|
758
|
+
else {},
|
759
|
+
}
|
760
|
+
|
761
|
+
_get_assertion_id_by_index = Method(BlockchainRequest.get_assertion_id_by_index)
|
762
|
+
|
763
|
+
def get_agreement_id(self, contract_address: Address, token_id: int) -> HexStr:
|
764
|
+
first_assertion_id = self._get_assertion_id_by_index(token_id, 0)
|
765
|
+
keyword = generate_keyword(contract_address, first_assertion_id)
|
766
|
+
return generate_agreement_id(contract_address, token_id, keyword)
|
767
|
+
|
768
|
+
_get_operation_result = Method(NodeRequest.get_operation_result)
|
769
|
+
|
770
|
+
@retry(catch=OperationNotFinished, max_retries=5, base_delay=1, backoff=2)
|
771
|
+
def get_operation_result(
|
772
|
+
self, operation_id: str, operation: str
|
773
|
+
) -> NodeResponseDict:
|
774
|
+
operation_result = self._get_operation_result(
|
775
|
+
operation_id=operation_id,
|
776
|
+
operation=operation,
|
777
|
+
)
|
778
|
+
|
779
|
+
validate_operation_status(operation_result)
|
780
|
+
|
781
|
+
return operation_result
|